目录
2.static成员不单单属于一个对象,属于整个类,属于所有的对象。
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析构