前面我们了解了类的构造函数,知道了构造函数体赋值,其实C++构造函数中还有一个初始化列表也可以进行初始化。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟着一个放在括号中的初始值或表达式
初始化列表也是构造函数的一部分,不要把初始化列表和构造函数认为是2个分离的部分
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
此时,我们定义一个对象:Date d1
,定义一个对象的同时就把成员变量整理地给定义了,但是每个成员变量单独是在哪里定义的呢?
答案就是:对象成员是在初始化列表中定义的,初始化列表是成员定义的位置
假如不写初始化列表,运行的时候还是会走初始化列表的,因为初始化列表是成员定义的位置,并且因为没有显式写初始化列表,所以内置类型不做处理,自定义类型调用其默认构造函数
需要注意的是:每个成员变量在初始化列表中最多只能出现一次
既然构造函数体就可以进行成员的初始化,那么还引入初始化列表有什么用呢?
是因为有三种类型成员在构造函数体内无法初始化,只能用初始化列表,这三个成员是:
引用和const的特征是必须在定义的时候初始化,所以不可以在构造函数体内进行初始化,只能在初始化列表中定义时初始化
class A
{
public:
A(int a, int b)
:_a(a)
, _b(b)
{}
private:
const int _a;
int& _b;
};
有默认构造函数的自定义类型成员,即使我们不去在初始化列表中显式写初始化,编译器也会自动调用它的默认构造
假如这个类没有默认构造,编译器没有可以自动调用的了,所以要在初始化列表中初始化
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int b)
:_aa(a)//对无默认构造函数的类对象成员的初始化
{}
private:
A _aa;
};
其实不是所有初始化都可以在初始化列表里初始化,还是可以在构造函数体内赋值。比如一些比较复杂的初始化,只能在函数体内进行
就比如下面的栈,对其中动态资源的初始化,就不能用初始化列表实现:
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class C
{
public:
C(int a, int b, int c)
:_a(a)
, _b(b)
, _c(c)
{}
private:
int _b;
int _c;
int _a;
};
这里虽然在初始化列表中是先初始化_a
,然后_b
,最后_c
但是初始化顺序依照声明的顺序,所以正确的初始化顺序是_b
->_c
->_a
声明为stack的类成员称为类的静态成员
用stack修饰的成员变量,称为静态成员变量
用stack修饰的成员函数,称之为静态成员函数
sizeof
计算类的大小时,是不计算静态成员变量的。普通成员变量,属于每个类对象,存储在对象里。静态成员函数不可以调用非静态的成员函数,因为非静态成员函数的调用需要this指针
非静态成员函数可以调用静态成员函数,因为在类内不受类域和访问限定符限制
class A
{public:
A(int a)
:_a(a)
{
_b++;
}
static void showB()
{
cout << _b << endl;
}
private:
int _a;
static int _b;
};
int A::_b = 0;//类外定义
int main()
{
A::showB();
A a1(2);
a1.showB();
return 0;
}
友元函数是定义在类外部的普通函数,它可以访问类的私有成员,它不属于类,但是需要在内中声明,声明时前面加上friend关键字
友元函数,就是把函数作为“朋友”,好朋友可以到自己家里访问,同理,友元函数就可以访问类中私有成员
class A
{
friend void showAB(const A& a);//友元函数声明
public:
A(int a, int b)
:_a(a)
, _b(b)
{
}
private:
int _a;
int _b;
};
void showAB(const A& a)//友元函数
{
cout << a._a << a._b << endl;
}
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的私有和保护成员
class A
{
friend class B;
public:
A(int a, int b)
:_a(a)
, _b(b)
{}
private:
int _a;
int _b;
};
class B
{
public:
B(int c)
:_c(c)
{}
void show(const A& a)
{
cout << _c << a._a << a._b << endl;
}
private:
int _c;
};
int main()
{
A a(1, 2);
B b(3);
b.show(a);
return 0;
}
类的友元关系是单向的,没有交换性,上面的代码中,B是A的友元类,B中成员函数可以访问A中的私有成员,但是A中的成员函数是不可以访问B中私有成员的
友元关系不能传递
友元关系不能继承
如果一个类定义在另一个类的内部,这个类就叫做内部类
class A
{
public:
class B
{
public:
void show(const A& a)
{
cout << a._a <<" "<< a._b << endl;
}
};
private:
int _a = 1;
int _b = 2;
};
int main()
{
A::B b;
b.show(A());
}
匿名对象:就是在定义对象的时候不取名字
class A
{
public:
A(int a, int b)
:_a(a)
, _b(b)
{
cout << "A(int a,int b)" << endl;
}
private:
int _a;
int _b;
};
int main()
{
A(1, 2);//匿名对象
}
匿名对象的生命周期只有一行,所以运行完匿名对象定义的一行后,匿名对象就会被销毁
需要用const引用接收:
const A& a = A(1, 1);
const引用可以延长匿名对象的生命周期,生命周期在当前函数作用域
假如const引用不会延长生命周期
用const引用接收了一个匿名对象后,运行完这行后匿名对象就销毁了
而那个const引用也就成了一个“野引用了”
这就是const引用可以延长匿名对象的生命周期的原因
下面有一个类:
class A
{
public:
A(int a = 4)
:_a(a)
{
cout << "A(int a = 4)" << endl;
}
A(const A& a)
{
_a = a._a;
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
构造函数对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还有类型转换作用
int main()
{
A a = 2;//隐式类型转换
return 0;
}
类型转换会产生临时变量,用2调用构造函数,建立临时变量,再用拷贝构造将临时变量拷贝给a,这里一行就调用了构造函数和拷贝构造,编译器就会进行优化,直接用2构造
在一个表达式中,连续构造+拷贝构造 ->优化为直接构造
这种隐式类型转换,适用的地方在哪呢?
如果有这样的函数
void f1(A a)
{}
以为我们想传参需要先建立一个对象,在传
int main()
{
A a(1);
f1(a);
return 0;
}
有了隐式类型转换,就可以减少一步:
int main()
{
f1(1);
return 0;
}
在一个表达式中,连续构造+拷贝构造编译器一定会优化成一个拷贝构造
前面的隐式类型转换其实就是一个典型的例子
下面我们看一下其他情况的连续构造+拷贝构造:
f1(A(1))
这里先是A(1)
调用了构造函数,然后再通过拷贝构造,拷贝出一个临时变量,传给形参,所以编译器会其优化成一个构造函数
这里先调用构造函数 构造出一个匿名对象 然后用这个匿名对象去拷贝构造形参 编译器会觉得反正都是临时的 不如直接用这个构造函数去直接构造形参 所以实际上可能只会调用一次构造函数
现有一个函数
A f2()
{
A aa;
return aa;
}
A a = f2()
:
f2()
返回值先拷贝出一个临时变量,临时变量再拷贝给a
同一行一个表达式中连续2个拷贝构造会优化成一次拷贝构造
连续拷贝构造+赋值重载是无法优化的
A a;
a = f2();