
目录
[①A a(10);](#①A a(10);)
[②A a = 10;](#②A a = 10;)
(1)初始化列表
①概念
在类中,语法上,我们把类的属性叫做声明,实现在类的构造函数中。在构造函数中我们可以通过函数体来实现类的属性,也可以通过初始化列表来实现类的属性。
初始化列表就是类的属性默认的值
②语法
cpp
类名(参数列表)
:属性1(表达式1)
,属性2(表达式2)
, .........
{函数体 }
cpp
#include<iostream>
using namespace std;
class Date {
public:
Date(int year = 1946, int month = 2, int day = 14);
void Print();
private:
int _year;//声明部分
int _month;
int _day;
};
Date::Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{ }
③初始化次数
每个属性在初始化列表中只能出现一次。
④C++11标准
C++11以后,支持在类属性声明的位置给缺省值。
⑤定义时声明、初始化列表、函数体三者执行顺序
初始化列表(定义时赋值) > 函数体 > 定义时声明(给定默认值)
定义时声明只有一种情况才有用,就是我初始化列表和函数体里都没有初始化;如果初始化列表和函数体都对某一数据初始化了,最终的结果是函数体里的结果。

⑥必须写在初始化列表里的三种情况
const、引用、无默认构造的类(你已经手动写了带参的构造函数)对象初始化时只能写在初始化列表的位置。C++11以后可以写在定义的位置。
cpp
class Data {
public:
Data(int val,int& a);
private:
const int _val;//被const修饰的属性必须写在初始化列表中
int& _a;//引用初始化必须写在初始化列表中
};
Data::Data(int val, int& a)
:_val(val)
, _a(a)
{}
cpp
class A {
public:
A(int val) //写了带参的构造函数,这个类就是无默认构造的类,
//作为其它类属性时就只能写在初始化列表里
:_val(val)
{ }
private:
int _val;
};
class B {
public:
B()
:a(20)
{ }
private:
A a;
};
⑦存在问题
无论你写不写初始化列表每个构造函数都默认有。
你不写,编译器会自动生成带初始化列表的构造函数。你写了构造函数但是没有写初始化列表,编译器会自动在你写的构造函数里插入隐式初始化列表(编译器自动生成、你看不见的初始化列表;内置类型是随机值,自定义类型会调用默认构造)
每一个类属性只能在初始化列表中存在一次。
不管你在初始化列表中对不对某个类属性进行初始化,都会走初始化列表。
⑧类属性的顺序问题
对类属性的初始化顺序只依照于定义时的顺序,与声明的顺序无关。
cpp
class Date {
public:
Date(int year, int month, int day);
private://定义
int _year;
int _month;
int _day;
};
Date::Date(int year,int month,int day)//声明
:_day(day)//按照定义时初始化的顺序进行初始化
,_month(month)
,_year(year)
{ }
练习:
cpp
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{
}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2 = 2;
int _a1 = 2;
};
int main()
{
A aa(1);
aa.Print();
}
打印的结果是1 随机值。
思路:先初始化_a2。此时_a1没有初始化就是随机值。再初始化_a1,此时_a1的值就是1。
(2)什么时候写构造函数
内置类型编译器默认生成的是随机值,需要我们写;自定义类型会自动处理,一般不需要我们写。
二、static成员
(1)概念
用static修饰的成员变量,称为静态成员变量。静态的类属性只能在类外初始化。
(2)本质
静态成员变量 = 类的全局变量
理解:
①存在于静态区。只初始化一次------不管类下有多少个对象,只初始化一次,被所有的对象共享。
②作用域就是类域,public修饰时可以在类外访问。
③生命周期贯穿整个程序。------存在于静态区,而静态区的生命周期就是贯穿整个程序。
(3)函数
静态成员函数没有this指针。
cpp
class Data {
public:
Data()
:_val(0)
{}
static void Print() {
cout << _val << endl;//没有this指针无法访问
}
private:
int _val;
};
静态成员函数不能访问类的成员变量(没有this指针);可以访问其它静态成员变量(静态成员变量 = 类的全局变量)。
非静态的成员函数,可以访问类的任意成员变量。(有this指针)
(4)静态成员的访问
静态的成员变量和函数,可以通过作用域解析符(::)或成员访问运算符(.)访问。(::静态成员,.静态成员函数)
(5)与构造函数的关系
静态成员变量不走构造函数。(存在于静态区,与类没有关系,只是相当于类的全局变量)
三、友元
(1)概念
提供了一种突破类访问限定符的封装方式。(简单来说就是非类成员也可以访问private里的成员变量),分为友元函数和友元类。
(2)友元函数
友元函数不是类的成员函数,而是普通的函数(只是可以访问private的成员变量),也不存在this指针的概念。一个函数可以是多个类的友元函数。
使用友元函数,必须在想要访问的成员变量的类里面进行声明。(前面加上friend)
cpp
class Data {
public:
Data(int val)
:_val(val)
{ }
friend void Print(Data& data);
private:
int _val;
};
void Print(Data& data) {//friend声明后可以正常使用private
cout << data._val << endl;
}
(3)友元类
①概念
整个类都被授权可以访问另一个类的private。(包括所有成员)
cpp
class A {
public:
A(int a):_a(a){}
friend class B;//类B是A的友元
private:
int _a;
};
class B {
public:
B(int b) :_b(b) {}
void Print(A& a) {//可以访问A的private
cout << a._a << " " << _b << endl;
}
private:
int _b;
};
int main() {
A a(1);
B b(2);
b.Print(a);
}
②注意
Ⅰ.友元类的关系是单向的。例如:B是A的友元,A不是B的友元,除非你声明两次。
Ⅱ.友元类的关系不能传递。例如:A是B的友元,B是C的友元。A和C没有关系。
(4)限定
友元函数和友元类都不受访问限定符的限定吗,可以在任意访问限定符的位置进行声明。
(5)优缺点
虽然提供了便利,但是破环了封装,增加了耦合性(模块之间的依赖程度)。
四、内部类
(1)概念
存在一个类内部的类。和外部类没有任何关系,只是受外部类类域(只能通过外部类访问内部类------外部类::内部类)和访问限定符(决定内部类是否能被外部访问)的限定。内部类默认为外部类的友元。
cpp
class Outer {
public:
class Inner {
public:
Inner(int val) :_val2(val) {}//内部类(存在public下,可以被外部看见)
void Print(Outer& out) {//内部类默认是外部类的友元
cout << out._val1 << endl << _val2 << endl;
}
private:
int _val2;
};
Outer(int val):_val1(val){}
private:
int _val1;
};
int main() {
Outer out(10);
Outer::Inner in(20);//内部类受外部类域的限定
in.Print(out);
}
(2)什么时候用内部类
当B类的实现就是为了给A用就可以考虑将B类放到A类内部。放到public里说明想要让外部使用,放到private/protect里说明只想外部类使用内部类。
五、匿名对象
(1)概念
用类型(实参)定义出来的对象。只能用一次,生命周期仅存在于当前这一行。匿名对象就是临时对象/临时变量。
cpp
class A {
public:
A(int val):_val(val){}
void Print() {
cout << _val << endl;
}
private:
int _val;
};
int main() {
A(10).Print();//匿名对象的使用
}
(2)const引用修饰匿名对象
const引用修饰匿名对象,会将匿名对象的生命周期延长到const引用销毁。但是匿名对象为只读。
cpp
class A {
public:
A(int val):_val(val){}
void Print() const{
cout << _val << endl;
}
private:
int _val;
};
int main() {
const A& val = A(10);
val.Print();
}
六、类型转换
(1)概念
类型转换分为显示类型转换和隐式类型转换。显示类型就是前面加个括号,括号里是类型名。隐式类型转换就是编译器偷偷帮你转。
(2)两种方式构造对象的区别
①A a(10);
直接把10传给构造函数,不存在类型转化
②A a = 10;
"="两边的操作符类型不一致,所以会发生隐式类型转换。
转化过程:编译器看到的是A a = A(10)。会先调用构造函数先初始化这个匿名对象,接着调用拷贝构造函数将匿名对象拷贝给对象a,完成对a的构造。
(3)隐式类型转化
C++支持将内置类型隐式转化为类类型对象。但是必须存在相关内置类型的构造函数。
①单参数的构造函数
cpp
class A {
public:
A(int val):_val(val){}
void Print() const{
cout << _val << endl;
}
private:
int _val;
};
int main() {
A a = 10;//进行了隐式类型转化(单参数的构造函数类型转化)
a.Print();
}
②多参数的构造函数
C++11以后,支持多参数的隐式转化
cpp
class Date {
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{ }
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date date = { 2026,3,29 };//C++11以后支持多个参数的隐式类型转化
date.Print();
}
③explicit
关键字之一。禁止隐式类型转化。
cpp
class A {
public:
explicit A(int val) :_val(val) {}//explicit修饰后,不再支持隐式类型转化
void Print() const {
cout << _val << endl;
}
private:
int _val;
};
int main() {
A a = 10;//编译错误,构造函数已经被explicit修饰
}
(4)两个类
不同的类对象之间也可以隐式转化,需要相应的构造函数。
cpp
class B;
class A {
public:
A(int a) :_a(a) {}
void Print() {
cout << _a << endl;
}
friend B;
private:
int _a;
};
class B {
public:
B(A a) {//用A构造B
_b = a._a;
}
void Print() {
cout << _b << endl;
}
private:
int _b;
};
int main() {
A a(10);
a.Print();
B b = a;
b.Print();
}
七、拷贝优化
现代编译器为了提高效率,在不影响正确率的情况下。会减少一些传参和传返回值过程中的优化。优化过程完全取决于编译器,C++标准中没有明确的规定。
cpp
class A {
public:
A(int val)
:_val(val)
{
cout << "构造完成" << endl;
}
A(const A& a)
{
_val = a._val;
cout << "拷贝构造完成" << endl;
}
private:
int _val;
};
A test() {
A a(10);
return a;//返回时会创建一个临时对象,存储对象a;会调用拷贝构造
}
int main() {
test();//只会打印构造完成,拷贝构造被优化掉了
}