类和对象(初始化列表,static成员,友元,内部类)

本文详细解释了C++中的构造函数初始化列表、隐式类型转换、静态成员、友元关系、内部类和匿名对象的概念,以及编译器的优化策略,帮助读者掌握这些关键概念在实际编程中的应用。
摘要由CSDN通过智能技术生成

目录

1.构造函数 初始化列表

1.什么是初始化列表

2.初始化列表的特点

3.单参数构造函数(隐式类型转换)

explicit关键字(禁止隐式类型转换)

多参数(隐式类型转换)

2. static成员

1.定义

2.特点

1.static成员不存在于对象中,存在于静态区上。

2.static成员不单单属于一个对象,属于整个类,属于所有的对象。

3.友元

1 友元函数

2.友元类

4.内部类

1.定义

2.特点

5.匿名对象

6.拷贝对象时编译器的一些优化

1.连续的一个表达式中,构造+拷贝构造会优化成构造

2.连续的一个表达式中,拷贝构造+拷贝构造会优化成一个拷贝构造


1.构造函数 初始化列表

1.什么是初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。({} 是函数体)

class Date
{
public:
 Date(int year, int month, int day)//初始化列表
 : _year(year)
 , _month(month)
 , _day(day)
 {}

private:
 int _year;
 int _month;
 int _day;
};

初始化列表相当于定义变量的位置。

2.初始化列表的特点

1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。

2.定义时必须初始化的变量,一定要在初始化列表初始化。(可以理解为初始化列表是定义的地方,函数体是赋值的地方)

eg. 1.引用类型变量& 

      2.const 修饰的变量

      3.自定义类型变量(没有默认构造函数)

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity )
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

class MyQueue
{
public:
	MyQueue(int n, int& x)
		:_pushst(n)
		//, _popst(n)//自定义类型(无默认构造函数)
		, _size(0)
		//, _b(1)//consr修饰
		//, _x(x)//引用
	{
		_b = 1;
		_x = x;
		_popst = 1;

	}
private:
	Stack _pushst;
	Stack _popst;
	size_t _size;
	const int _b;
	int& _x;
};

1.引用类型,定义的时候要指名引用对象。int a=1; int& b=a;

2.创建const修饰的变量开始就要给值。const int a=1;

3.创建自定义类型变量(无默认构造函数),需要传参。Stack popst(4);

3.如果在初始化列表中没有显试写成员变量的初始化

   1.对于自定义类型成员,会调用它自己的默认构造函数(不用传参的)(如果没有就会报错)

   2.对于内置类型成员,会赋给它缺省值(如果没有 看编译器会不会处理)

      (自定义类型变量也可以给缺省值,但一般不用)

可以看出我们并没有写初始化列表,但自定义类型成员仍然调用了默认构造进行了初始化,而内置类型成员_size也根据缺省值进行了初始化。

注意:成员进行初始化,先走初始化列表,再走函数体。

           实践当中尽量用初始化列表进行初始化。

函数体也不是完全没有用,下面这种情况我们就需要用到函数体。

4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

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();
}

上面这段代码,因为_a2先声明的,所以先初始化_a2,此时_a1并没有进行初始化,所以给_a2赋值是随机值。

3.单参数构造函数(隐式类型转换)

如果一个类的构造函数需要传一个参数,创建类时我们可以这样写A a(1); 或者写成A a=1;这样的形式。这两种形式有什么不同呢?

A a(1); 只调用了构造函数。

A a=1;调用了构造函数 和 拷贝构造函数

这是因为发生了隐式类型转换。

int 1 通过构造函数创建一个临时变量(与a同类型),再由临时变量通过拷贝构造函数赋给a。(编译器遇到连续的 构造+拷贝构造 会优化成 构造)

class A
{
public:
	A(int a)//构造函数
		:_a(a)
	{
		cout << "构造"<<endl;
	}
	A(const A& a)//拷贝构造函数
	{
		_a = a._a;
		cout << "拷贝"<<endl;
	}
private:
	int _a;
};
int main() {
	A a1(1);//普通调用构造函数
	A a2 = 1;//发生隐式类型转换 
	//通过构造函数产生一个临时变量,再由临时变量通过拷贝构造函数赋给a2

}

下面代码为什么a3不能直接引用2呢?

这并不是因为类型不一样,而是隐式类型转换产生的临时变量有常性,不能改变,也就是发生了权限的放大。

加const 就可以解决

此时a3是引用的int 2类型转换构造的临时变量。不会发生拷贝构造

这样的隐式类型转换有什么用呢?

如果没有隐式类型转换那么下面代码需要这样写

class A
{
public:
	A(int b)
	{
		_b = b;
	}
	int _b;
};
class Stack
{
public:
	Stack(A a)
	{
		int _a = a._b;
	}
private:
	int _a;
};
int main() 
{
	A a(1);
	Stack aa(a);
}

先建立A类的变量,再传给aa;

但通过隐式类型转换我们就可以这样写(传int 类型转换)

explicit关键字(禁止隐式类型转换)

explicit 禁止 int  转换为A类型。

多参数(隐式类型转换)

传参时把多个参数用{}括起来。

A a(1,2,3)也可以写成A a{1,2,3};

2. static成员

1.定义

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化(声明在类内,定义在类外,相当于函数声明定义分离)

class A
{
public:
	A() { ++_scount; }
private:
	static int _scount;//声明
};
int A::_scount = 0;//定义
//光有定义,没有声明会报错

2.特点

1.static成员不存在于对象中,存在于静态区上。

提问:static int _scount=1;给缺省值可以吗?

no,因为缺省值是给初始化列表的,而_scount初始化在类外,不通过初始化列表初始化。

2.static成员不单单属于一个对象,属于整个类,属于所有的对象。

利用这个我们就可以记录一共创建了几个对象(每调用一次构造函数,静态成员++)

记录现在存在的对象(构造++,析构--)

上面是把_scount设为共有来访问的,有没有其它方法来解决呢?

我们可以设一个静态函数(没有this指针 只可以访问静态成员)

如果不设静态函数,普通Get函数也可以访问静态成员。int _Getcount() {return _scount;}

但要通过对象才能调用 cout<<a2._Getcount();

总结:

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

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

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

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

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

3.友元

1 友元函数

这个之前我们也提到过了,当一个全局函数想访问类的私有变量。

我们可以在类中表明这个全局函数为友元函数。

class A
{
	friend int Getcount();
public:
	A()
		:_a(1)
		, _b(2)
	{
		_scount++;
	}

	
private:
	static int _scount;//声明
	int _a;
	int _b;
};
int Getcount()
{
	return A::_scount;
}
int A::_scount = 0;//定义
A TestA()
{
	A a1;
	return a1;//按理说会有两次构造调用,但没有接收值,编译器会优化掉
}
int main()
{

	TestA();
	A a2;
	A a3;
	cout << Getcount();
}

1 友元函数可访问类的私有和保护成员,但不是类的成员函数

2 友元函数不能用const修饰

3 友元函数可以在类定义的任何地方声明,不受类访问限定符限制

4 一个函数可以是多个类的友元函数

5 友元函数的调用与普通函数的调用原理相同

2.友元类

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

class B
{
	friend class A;//A类是B的朋友 A可以访问B
public:
	B()
	{
		_scount++;
	}
private:
	static int _scount;//声明
};
int B::_scount = 0;//定义
class A
{
	
public:
	static int Getcount()
	{
		return B:: _scount;
	}

	
private:
	
	int _a;
	int _b;
};


int main()
{
	B b1;
	cout << A::Getcount();
}

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

A类是B的朋友 A可以访问B,但B不可以访问A

2.友元关系不能传递

如果B是A的友元,C是B的友元,则不能说明C时A的友元。

4.内部类

1.定义

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

class A

{
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元 (B可以访问A私有,但A不能访问B私有)
	{

	public:

		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
     };
};
int A::k = 1;
int main()
{
	A::B b;
	A a;
	b.foo(a);
	return 0;
}

2.特点

1.内部类B天生就是A的友元 (B可以访问A私有,但A不能访问B私有)

2.内部类B和外部类A,是一种平行关系。sizeof(外部类)==外部类变量占的大小 和内部类的变量大小无关。

3.内部类受外部类限制,需要通过外部类才能访问到内部类(A::B b;)

如果把内部类变为私有,相当于把内部类变为外部类的专属类。

5.匿名对象

简单来说,匿名对象就是没有名字的对象。与普通对象相比它的生命周期只在当前行中有效。

(匿名对象和临时对象都是有常性的,不能改变)

6.拷贝对象时编译器的一些优化

1.连续的一个表达式中,构造+拷贝构造会优化成构造

2.连续的一个表达式中,拷贝构造+拷贝构造会优化成一个拷贝构造

传值传参 为什么没有发生拷贝构造呢?A ret A a 再加上临时变量析构应该有3个析构

因为我用的是vs2022,优化等级会比较高,进行跨行优化。可以理解为,编译器没有构造a,底层用指针实现,a相当于ret的引用。

按理来说应该是这样的:1.A ret构造 2.A a构造 3.a拷贝构造临时变量 4.a析构 5.临时变量赋值重载ret 6.临时变量析构 7.出输1111111 8.ret析构

但编译器进行优化把a当作临时变量:1.A ret构造 2.A a构造 3.a赋值重载ret 4.a析构 5.输出111111

6.ret析构

  • 19
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值