C++之类和对象(3)

目录

[1. 再谈构造函数](#1. 再谈构造函数)

[1.1 构造函数体赋值](#1.1 构造函数体赋值)

[1.2 初始化列表](#1.2 初始化列表)

[1.3 explicit](#1.3 explicit)

[2. static成员](#2. static成员)

[2.1 概念](#2.1 概念)

[3. 友元](#3. 友元)

[3.1 友元函数](#3.1 友元函数)

[3.2 友元类](#3.2 友元类)

[4. 内部类](#4. 内部类)

[5. 匿名对象](#5. 匿名对象)

[6. 拷贝对象时编译器做出的优化](#6. 拷贝对象时编译器做出的优化)


1. 再谈构造函数

1.1 构造函数体赋值

class Date
{
public:
    Date(int year=2024, int month=3, int day=16)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void pri() {
        cout << _year <<" "<< _month <<" "<< _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main() {
    Date a;
    Date b(111);
    a.pri();
    b.pri();
    return 0;
}

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

1.2 初始化列表

class Date
{
public:
	Date(int year, int month, int day)
        //初始化列表
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void pri() {
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Date a(1,2,3);
	a.pri();
	return 0;
}


初始化列表是每个成员变量定义初始化的位置
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

const成员变量无法在函数体初始化,得在初始化列表初始化

int& ref;引用也得在初始化列表,因为引用定义必须初始化

自定义类型成员(且该类没有默认构造函数时)也在初始化列表

class A
{
public:
    A(int a)
        :_a1(a)
        , _a2(_a1)
    {}

    void Print() {
        cout << _a1 << " " << _a2 << endl;
    }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();
}

最后输出 1 -858993460

因为先初始化a2因为a2先在类中声明而且用的是a1的值初始化而a1是随机值

a1最后初始化为a,是1
能用初始化列表就用初始化列表

成员变量的顺序与初始化列表的顺序最好一致,因为****成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关

1.3 explicit

单参数类型:

class A {
public:
	A(int a=0) 
	:_aa(a)
	{
		cout <<_aa << endl;
	}
	
private:
	int _aa;
};
class B {
private:
	int _a;
	int* p = nullptr;
	int* pp = (int*)malloc(4);
};
int main() {
	B bb;
	A c1(1);
	A c2 = 2;//单参数构造函数支持隐式类型转换
	//所以是2构造一个临时对象然后拷贝构造
	const A& c3 = 4;
	return 0;
}

缺省值可以是其他的变量不局限于常量

如果不想存在隐式类型转换的话可以加explicit在构造函数前面:
c2和c3就出错了

多参数类型:

class A {
public:
	 A(int a=0,int b=1) 
	:_aa(a)
    ,_bb(b)
	{
		cout <<_aa <<" ";
		cout << _bb << endl;
	}
	
private:
	int _aa;
	int _bb;
};
class B {
private:
	int _a;
	int* p = nullptr;
	int* pp = (int*)malloc(4);
};
int main() {
	B bb;
	A c1(1,2);
	A c2 = {3,4};
	const A& c3 = {5,6};
	return 0;
}

多参数支持花括号产生隐式类型转换
同样加了explicit就不行了:

用explicit修饰构造函数,将会禁止构造函数的隐式转换

2. static成员

2.1 概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

静态成员函数没有this指针所以他只是为了访问静态成员变量他访问不了非静态成员变量因为没this

class A
{
public:
    A() {
        ++n;
    }
    A(const A& aa) {
        ++n;
    }
//private:
    static int n;//声明
    //属于整个类,但本质还是静态全局变量
};
int A::n = 0;//在类外定义
void func() {
    n += 1;//那么这里就访问不了n了
}
int main() {
    A a;
    A b;
    cout << n << endl;//这里也是访问不了n
    return 0;

}
class A
{
	
	public:
		A() { ++_scount; }
		A(const A & t) { ++_scount; }
		~A() { --_scount; }
		static int GetACount() { return _scount; }
	private:
		static int _scount;
	};
	int A::_scount = 0;
	void TestA()
	{
		cout << A::GetACount() << endl;
		A a1, a2;
		A a3(a1);
		cout << A::GetACount() << endl;
	}
	int main() {
		TestA();
		return 0;
	}

最终结果0 3

当创建对象 a1 时会调用默认构造函数 A()

输出 _scount 的初始值,初始值为 0。

创建对象 a1,_scount 自增为 1。

创建对象 a2,_scount 再次自增为 2。

通过复制构造函数创建对象 a3,_scount 再次自增为 3。

输出 _scount 的最终值,值为 3。
注意:

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

3. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元破坏了封装,所以友元不宜多用-慎用

3.1 友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{
	//友元函数
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
		
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
 
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1;
	cin >> d1;
	cout << d1;
	return 0;
}

友元函数是普通的全局函数:

#include <iostream>
 
using namespace std;
 
class A
{
 
 private:
     int A;
  public:
     print(){};
     
     //声明全局函数 person 是 类A 的友元函数
     friend void person (int &x);
}
 
 
void person(int &x)
{
   //使用了类A的成员变量age
   cout << "age=" << p.age << endl;
  
}
 
int main ()
{
   A p(22);
   person(p);
   return 0;
}

友元函数是其他类的成员函数:

#include <iostream>
 
using namespace std;
 
class A
{
 
 private:
     int A;
  public:
     print(){};
     
     //声明类B的成员函数 person 是 类A 的友元函数
     friend  void B::person (int &x);
}
 
 
class B
{
  private:
     int B;
  public:
     person(int &x);
 
}
 
B::person(int &x)
{
   //因为类B的成员函数person是类A的友元函数,所以看可以使用类A的成员变量age
   cout << "age=" << p.age << endl;
  
}
 
int main ()
{
   A p(22);
 
   B q;
   q.person(p);
   return 0;
  
}

注意:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

3.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元关系是单向的,不具有交换性。

比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接

访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

友元关系不能传递:如果C是B的友元, B是A的友元,则不能说明C时A的友元。

友元关系不能继承(后续解析)

//时间类
class Time
{
   friend class Date;   
// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
 
public:
 
 //成员函数的初始化列表
 Time(int hour = 0, int minute = 0, int second = 0)
 : _hour(hour)
 , _minute(minute)
 , _second(second)
 {}
   
private:
   int _hour;
   int _minute;
   int _second;
};
 
 
//日期类
class Date
{
public:
 
   //成员函数的初始化列表
   Date(int year = 1900, int month = 1, int day = 1)
       : _year(year)
       , _month(month)
       , _day(day)
   {}
   
   void SetTimeOfDate(int hour, int minute, int second)
   {
       // 直接访问时间类私有的成员变量,因为日期类是时间类的友元类
       _t._hour = hour;
       _t._minute = minute;
       _t._second = second;
   }
   
private:
   int _year;
   int _month;
   int _day;
   Time _t;
};

优缺点总结 :

点:友元函数不是类的成员但是却具有成员的权限,可以访问类中受保护的成员,这破坏了类的封装特性和权限管控;

优点:可以实现类之间的数据共享;比如上面互为友元类,则可以互相访问对方受保护的成员;

总结:友元函数是一种破坏封装特性的机制,可以让程序员写代码更灵活,但是不能滥用

4. 内部类

概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类, 更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性:

    1. 内部类可以定义在外部类的public、protected、private都是可以的。
    1. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
    1. sizeof(外部类)=外部类,和内部类没有任何关系。

      // 1、B类受A类域和访问限定符的限制,其实他们是两个独立的类
      // 2、内部类默认就是外部类的友元类
      class A
      {//外部类不能访问内部类
      public:
      class B // B天生就是A的友元
      {
      public:
      void print(const A& a)
      {
      cout << k << endl; //可以直接访问A的静态成员变量
      cout << a.h << endl; //也可以访问A的成员变量
      }
      };
      private:
      static int k;
      int h;
      };
      int A::k = 1;

      int main()
      {
      A::B b;
      b.print(A());
      cout << sizeof(A) << endl; //8 外部类的大小与内部类无关 a与b相互独立的只不过b受a类的局域限制
      return 0;
      }
      类本身不占用空间

C++不太喜欢使用内部类,所以了解即可

5. 匿名对象

匿名对象的特点就是不用取名字,生命周期只存在定义的这一行。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
 
int main()
{
	A aa1;
	//A aa1();// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	A();// 但是他的生命周期只有这一行,紧接着它的下一步就会自动调用析构函数
 
	A aa2(2);
	return 0;
}

应用场景: 当我们做C++的OJ题时会发现都是将其封装在一个Solution类中的,假设我们需要调用这个类中的某一个函数,是需要先创建一个Solution的对象,然后通过这个对象进行调用,这样的话有点麻烦,我们可以直接使用匿名对象来调用这个类中的成员函数。

class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	// 1.基本方法
	Solution sl;
	sl.Sum_Solution(10);
 
	// 2.匿名对象
	Solution().Sum_Solution(10);
 
	return 0;
}

6. 拷贝对象时编译器做出的优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
 
	// 传值返回
	f2();
	cout << endl;
 
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
 
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
 
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
 
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}


相关推荐
一点媛艺2 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生3 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
pianmian13 小时前
python数据结构基础(7)
数据结构·算法
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程3 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye4 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go