目录
初始化列表总结:
不给缺省值时,不同类型的成员变量调用构造函数的情况:
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int& x, int year = 2025, int month = 10, int day = 1)
://_year(year)
t(1) // 自定义类型会去调用它的构造
, ref(x) // 引用在初始化时,不能引用局部对象,否则函数结束后局部变量销毁,会造成野引用,最好引用外面的一个变量
,_n(20)
// 进入函数体之前,认为成员变量定义的地方在初始化列表,所以那三类必须在初始化列表初始化
{
// 内置类型可以用初始化列表初始化,也可以在函数体内部初始化;也可以既在初始化列表初始化,又在函数体内部初始化(最好别这样)
// 建议都使用初始化列表
_month = month;
_day = day;
}
private:
// 构造函数的初始化顺序与成员变量的声明顺序有关,与初始化列表中的顺序无关
// 调用构造时,先初始化初始化列表中的变量(按照声明顺序),在初始化函数体内部的成员变量
// 这里是成员变量的声明,声明不开空间
int _year;
int _month;
int _day;
// 这三个成员变量都有一个共同特点:必须在定义时初始化
// 因为这些成员变量需要在定义时初始化,所以这时就要用到初始化列表,初始化列表就是它们定义的地方
Time t; // 没有默认构造
int& ref; // 引用
const int _n; // const常量
};
int main()
{
// 对象整体定义,开空间了
// 当创建类的对象时,才会为定义类中的成员变量,为他们分配内存空间
// 初始化分配空间时,是按成员变量声明的顺序分配的
Date d1;
return 0;
}
给缺省值时,不同类型的成员变量调用构造函数的情况:
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
// 尽量在初始化列表初始化,即使没有在初始化列表初始化的成员也会走初始化列表,因为初始化列表是每个成员变量定义的地方
// 必须在初始化列表初始化,但是不一定非要显示初始化,也可以给了缺省值
// 给了缺省值就可以不显示初始化,编译器自己用缺省值在初始化列表初始化,如果显示初始化了就用显示的
Date()
:_year(100)
//, _t(1)
, _x(100)
{
}
private:
// 成员变量的声明,缺省值,不是初始化
// 如果不显示的写构造函数,编译器默认生成就会用这个值
// 如果显示的写构造函数,就用显示初始化的值
// 如果既没有给缺省值,也没有显示初始化就是随机值或0,具体看编译器
int _year = 1;
int _month = 1;
int _day = 1;
const int& ref = 0;
int* ptr = (int*)malloc(40); // 缺省值也可以是malloc表达式
Time _t = 1; // 自定义类型变量也可以给一个缺省值,给一个整型是因为它的构造函数参数类型为整型
const int _x = 10; // 常量类型变量也可以给一个缺省值,给了之后它就会用这个缺省值在初始化列表初始化
};
int main()
{
// 对象整体定义,开空间了
Date d1;
return 0;
}
// 总结:
// 1、一般情况下,建议尽量用初始化列表显示初始化
// 2、如果没有在初始化列表初始化,尽量给缺省值
// 自定义类型有默认构造,会调用它自己的默认构造,不用初始化列表显示初始化
// 除了自定义函数可以调用自己的默认构造的,可以既不显示初始化也没有缺省值,其他的情况必须显示初始化或者给缺省值
// 函数体在做一些检查,或更深层次的初始化等情况使用
class A
{
// 除了自定义函数可以调用自己的默认构造的,可以既不显示初始化也没有缺省值,其他的情况必须显示初始化或者给缺省值
// 函数体在做一些检查,后更深层次的初始化等情况使用
public:
A(int n = 10)
:_a((int*)malloc(sizeof(int) * 10))
, _size(0)
{
// 检查
if (_a == nullptr)
{
perror("malloc fail!");
exit(-1);
}
// 更深层次的初始化,给数组赋值
memset(_a, 0, sizeof(int) * n);
}
private:
int* _a;
int _size;
};
构造函数初始化列表逻辑梳理

总结:在构造函数中使用初始化列表 一、如果不显示的初始化成员变量 1、成员变量类型为内置类型: a)有缺省值,那么就会用缺省值在初始化列表初始化 b)没有缺省值,那就是随机值 2、成员变量类型为自定义类型 a)有默认构造,有缺省值,传缺省值调用默认构造;有默认构造,没有缺省值,调用默认构造 b)没有有默认构造,有缺省值,传缺省值调用默认构造;没有默认构造,没有缺省值,报错 二、显示初始化成员变量 1、成员变量类型为内置类型: a)有缺省值,但是不会用缺省值,而是用初始化列表中的初始值 b)没有缺省值,就是初始化列表中的初始值 2、成员变量类型为自定义类型 a)有默认构造,有缺省值,用初始化列表中的值调用默认构造;有默认构造,没有缺省值,用初始化列表中的值调用默认构造 b)没有默认构造,有缺省值,用初始化列表中的值调用构造;没有默认构造,没有缺省值,用初始化列表中的值调用构造
下面这段代码会打印出什么?
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和一个随机值
}
(1)内置类型之间的转换:
// 类型转换
int main()
{
// 有一定的关联才能转换:
//
// <1> 内置类型 - 内置类型 之间转换
// 整型家族(表示的范围不一样)
// 整形和浮点型,都能表示数据大小(表示的方式不一样)
// 整型和指针,指针是地址的编号
// 浮点数和指针之间,不能转换(强制类型转换都不行),没有关联关系
int i = 1;
double d = i; // 隐式类型转换,C规定,相近的类型可以进行隐式类型转换
// 给d时,中间会生成一个double类型的临时变量(临时对象)
const double& ref = i;
// 中间产生的临时变量具有常性,要加const
//int* pi = &i;
//float f = (float)pi; //err
return 0;
}
(2)内置类型转换为类类型对象:
// <2> C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数
class A
{
public:
A(int a1)
:_a1(a1)
{
}
private:
int _a1 = 1;
int _a2 = 2;
};
// 引用传参一般都要加上const
void func(const A& aa = 1) // 引用的不是1,是临时对象
{}
class Stack
{
public:
void Push(const A& a)
{}
void Push(const string& str)
{}
private:
};
int main()
{
// 构造
A a1(1);
// 隐式类型转换
// 1构造⼀个A的临时对象,再用这个临时对象拷⻉构造a2
// 编译器遇到连续构造+拷贝构造->优化为直接构造
A a2 = 1;
// 1 可以通过构造函数转换为一个A的对象,这个对象是临时对象,它再拷贝构造给a2
// 为什么1能构造?因为A类的构造函数参数类型为整型,所以 1 能够通过构造函数转换为一个A的对象
const A& ref1 = a1;
const A& ref2 = 1; // 引用的是中间产生的临时对象,临时对象具有常性,要加const
func(1);
func(a1);
func(); // 也可以给缺省值
Stack st1;
A a3(3);
st1.Push(a3);
st1.Push(3); // 3能够通过构造函数转换为一个A的对象(编译器自动生成),这个对象是临时对象,然后这个对象会传给形参
string s1("xxxxx");
st1.Push(s1);
st1.Push("xxxxx");
return 0;
}
多参数情况和explict修饰
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a)
:_a1(a)
{}
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
// 构造
A a1(1);
// 类型转换
// 构造+拷贝构造 优化为-> 直接构造
A a2 = 1;
const A& ref1 = 0;
// 多参数
// 构造
A a3(1, 2);
// 多参数
// 类型转换
// 构造+拷贝构造 优化为-> 直接构造
A a4 = { 1,2 };
const A& ref2 = { 1,2 };
// 虽然编译器是直接构造,但是理论上还是{1,2}构造一个A的临时对象,再拷贝构造给ref2
// 而临时对象具有常性,所以要加const
A a5 = (1, 1); // 不能用小括号,编译器会解析为逗号表达式
// 逗号表达式:它将两个及其以上的式子联接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值
return 0;
}
(3)类类型之间的转换:
// <3> 类类型的对象(自定义类型)之间也可以隐式类型转换,需要相应的构造函数支持
// 与内资类型转换为类类型类似,构造函数中参数的类型为该类类型,才能隐式类型转换
class A
{
public:
// 构造函数explicit就不再支持隐式类型转换
A(int a1 = 1)
// explicit A(int a1)
:_a1(a1)
{
cout << "A(int a1)" << '\n';
}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
A(const A& aa) // 拷贝构造
{
cout << "A(const A& aa)" << '\n';
}
int Get() const
{
return _a1 + _a2;
}
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{
}
private:
int _b = 0;
};
int main()
{
A a1(1);
// 构造函数中参数的类型为该类类型,才能隐式类型转换
// 本质是a1作为参数调用B类的构造函数,转换为B类的对象,然后拷贝构造给b1
// 构造+拷贝构造 优化为-> 直接构造
B b1 = a1;
const B& ref = a1; // 引用的是临时对象,临时对象具有常性,要加const
return 0;
}
class A
{
public:
A(int a = 1)
:_a1(a)
{}
void Print1()
{
// 非静态成员函数可以访问任意的静态成员变量和静态成员函数
cout << _a1 << endl;
cout << _a2 << endl;
}
static void Print2() // 静态成员函数
{
// cout << _a1 << endl; // err 不能访问非静态的成员变量
cout << _a2 << endl;
}
// private:
int _a1 = 1;
static int _a2; // 静态成员变量,不能给缺省值
};
int A::_a2 = 1; // 静态成员变量一定要在类外进行初始化
int main()
{
A a;
A* ptr = nullptr;
// 无论是成员变量还是成员函数都受访问限定符的限制,public才能在类外访问,private则不能
// 访问非静态成员函数
a.Print1();
ptr->Print1(); // 这里有点bug,因为是空指针,所以无法访问内部成员变量,这里主要是理解可以通过指针访问成员函数
// A::Print1(); //err 非静态成员函数不能使用这种方法
// 访问静态成员函数
a.Print2();
ptr->Print2();
A::Print2(); // 这个最好
// 访问非静态成员变量
a._a1;
ptr->_a1; // // 这里有点bug,因为是空指针,所以无法访问内部成员变量,这里主要是理解可以通过指针访问成员变量
// A::_a1; //err 非静态成员变量不能使用这种方法
// 访问静态成员变量
a._a2;
ptr->_a2;
A::_a2;
return 0;
}
// static定义的成员变量生命周期是全局的(可以看成类专属的全局变量)
class A
{
public:
// A类型的对象通过构造或拷贝构造创建,这两个函数调用了多少次,就创建了多少个对象
A(int a = 1) // 构造
:_a1(a)
,_a2(a)
{
++_count;
}
// 拷贝构造
A(const A& t)
{
++_count;
}
// 静态成员函数
// 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针
// 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数
static int GetCount()
{
// _a1++; // 无法访问非静态的,因为没有this指针
return _count;
}
private:
// 声明
int _a1 = 1;
int _a2 = 1;
// public:
// 声明
static int _count; // 这个变量不存放在类中,它存放在静态区
// 可以理解为这个变量是这个类专属的,并且受到类域和访问限定符的限制的全局变量
// 因为它不再对象里面存放,所以它不会走构造函数
// 因此也不能给它缺省值,因为缺省值本质是,初始化列表没有初始化时,用这个值在初始化列表初始化,而初始化列表就是构造函数的一部分
};
int A::_count = 0;
int main()
{
A aa1;
cout << sizeof(aa1) << endl; // 8 对象的大小不包括静态成员变量
A* ptr = nullptr;
A aa2 = 1; // 这里是直接调用构造
// 因为_count是所有类共享的,每个对象访问的都是同一个
/*cout << ptr->_count << endl;
cout << aa1._count << endl;
cout << A::_count << endl;*/
// cout << _count << endl; //err
// 突破类域和访问限定符就可以访问静态成员,可以通过 类名::静态成员 或者 对象.静态成员 或者 指针-> 静态成员 来访问静态成员变量和静态成员函数。
// 只有静态成员函数可以通过类名直接调用
cout << A::GetCount() << endl;
cout << aa1.GetCount() << endl;
cout << ptr->GetCount() << endl;
return 0;
}
求下列代码的构造顺序和析构顺序?
C c;
int main()
{
A a;
B b;
static D d; // 局部static对象,都是在第一次运行到定义的位置时,才初始化
return 0;
}
// 析构时,先析构栈区,在析构静态区,全局对象和局部静态对象都在静态区,析构时也遵循后定义的先析构
// 注:局部对象在离开其作用域时析构(该段代码作用域为main函数)
// 静态局部对象和全局对象在程序完全结束时析构,而不在离开其作用域时(该段代码作用域是main函数),即main函数结束时
// 构造函数的调用顺序为:C A B D
// 析构函数调用的顺序为:B A D C
(1)友元函数
// 友元函数
// 前置声明,都则A的友元函数声明编译器不认识B
class B;
class A
{
// 友元函数声明
// 可以放在类内部的任意地方
friend void func(const A& aa, const B& bb); // 编译器都是向上找的,找它的声明或定义,而B类定义在A的下面,向上找不到,所以要在前面声明一下
private: // 这个友元函数声明在上定义在两个类的下面,所以两个类的细节都能用到
int _a1 = 1;
int _a2 = 2;
};
class B
{
// 友元函数声明
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
// 一个函数可以成为多个类的友元
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
(2)友元类
// 友元类
// 友元类是单向的,比如C是D的友元,D不是C的友元
// D要访问C,D要声明为C的友元
// 可以让他们互相成为友元
// 一个类中的成员函数可以成为另一个类的友元,但是比较麻烦,我们可以直接让这个类成为另一个类的友元
class C
{
friend class D; // 这是友元函数的声明,同时也算是类的前置声明,
// 但是如果用不到类D里面的细节,比如成员变量,声明和定义分离能很好的解决这个问题
public:
void func1(const D& dd)
{
// cout << dd._d1 << endl; // err 要访问dd对象中的成员变量,会向上找D类,但是上面只有声明,没有定义,所以访问不到D类内部的细节
cout << _c1 << endl;
}
void func2(const D& dd)
{
// cout << dd._d2 << endl; // err 访问不到D类内部的细节
// cout << _c2 << endl;
}
private:
int _c1 = 1;
int _c2 = 2;
};
class D
{
friend class C;
public:
void func1(const C& cc)
{
cout << cc._c1 << endl;
cout << _d1 << endl;
}
void func2(const C& cc)
{
cout << cc._c2 << endl;
cout << _d2 << endl;
}
private:
int _d1 = 3;
int _d2 = 4;
};
声明和定义分离解决访问不到类中成员的问题
// 声明和定义分离就能很好解决这个问题
// xxx.h
class C
{
friend class D; // 这是友元函数的声明,同时也算是类的前置声明,
// 但是如果用不到类D里面的细节,比如成员变量,声明和定义分离能很好的解决这个问题
public:
void func1(const D& dd);
void func2(const D& dd);
private:
int _c1 = 1;
int _c2 = 2;
};
class D
{
friend class C;
public:
void func1(const C& cc);
void func2(const C& cc);
private:
int _d1 = 3;
int _d2 = 4;
};
// xxx.cpp
void C::func1(const D& dd)
{
cout << dd._d1 << endl;
cout << _c1 << endl;
}
void C::func2(const D& dd)
{
cout << dd._d2 << endl;
cout << _c2 << endl;
}
void D::func1(const C& cc)
{
cout << cc._c1 << endl;
cout << _d1 << endl;
}
void D::func2(const C& cc)
{
cout << cc._c2 << endl;
cout << _d1 << endl;
}
内部类的使用
class A
{
public:
A(int n = 1)
:_a1(n)
, _a2(_a1)
{}
// void fun1(const B& refb) // classB的声明在该函数之后,向上找不到B类
// {}
// 内部类 是一个独立的类,不是A类的成员;受类域和访问限定符的限制
class B // B默认为A的友元,B中可以访问A中的成员
{
public:
B(int m = 1)
:_b1(m)
{}
void func2(const A& refa)
{
cout << refa._a1 << endl;
}
private:
int _b1;
};
//void fun1(const B& refb)
//{
// cout << refb._b1 << endl; // err 可以找到B类,但是A不是B的友元,无法访问B的成员变量,在B中加友元声明才能访问B
//}
private:
int _a1;
int _a2;
};
int main()
{
cout << sizeof(A) << endl; //8 A类中不报包含B类
A::B b;
A aa;
b.func2(aa);
return 0;
}
内部类的应用
class Solution {
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
};
public:
// 防止多次调用时累加
void clear()
{
_i = 1;
_ret = 0;
}
int Sum_Solution(int n) {
// 变长数组 vs不支持
// Sum arr[n];
Sum* ptr=new Sum[n];
delete[] ptr;
return _ret;
}
~Solution()
{
cout << "~Solution()" << endl;
}
private:
static int _i;
static int _ret;
};
int Solution::_i = 1;
int Solution::_ret = 0;
int main()
{
Solution s;
cout << s.Sum_Solution(5) << endl; // 15
s.clear();
cout << s.Sum_Solution(3) << endl; // 6
return 0;
}
匿名对象的使用
class Solution {
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
};
public:
// 防止多次调用时累加
void clear()
{
_i = 1;
_ret = 0;
}
int Sum_Solution(int n) {
// 变长数组 vs不支持
// Sum arr[n];
Sum* ptr=new Sum[n];
delete[] ptr;
return _ret;
}
~Solution()
{
cout << "~Solution()" << endl;
}
private:
static int _i;
static int _ret;
};
int Solution::_i = 1;
int Solution::_ret = 0;
void Func(const Solution& s = Solution()) // 给匿名对象缺省值;const引用会延长匿名对象的生命周期
{}
// 匿名对象
// 之前我们定义的对象都叫有名对象
int main()
{
Solution s; // 有名对象
cout << s.Sum_Solution(10) << endl;
s.clear();
// 生命周期只在当前一行,下一行它就销毁了
// Solution(); // 匿名对象
// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说
cout << Solution().Sum_Solution(10) << endl; // 匿名对象只能在当前行使用
Func(Solution()); // 匿名对象具有常性
const Solution& ref = Solution(); // const引用会延长匿名对象的生命周期
Func(Solution());
Func(s);
Func();
return 0;
}
// 对象拷贝时的编译器优化
class A
{
public:
A(int a = 0)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 1;
};
void f1(A aa)
{}
//int main()
//{
// // 传值传参
//
// // 构造+拷贝构造 优化-> 构造
// A aa1 = 1;
// cout << "--------------------" << endl;
//
// // 有名对象不优化,直接拷贝
// f1(aa1);
// cout << "--------------------" << endl;
//
// // 原本应该是把1作为参数构造一个临时对象,然后这个临时对象再拷贝构造传给函数,编译器优化为直接构造然后传给函数
// f1(1); // 隐式类型转换
// cout << "--------------------" << endl;
//
// // 同上
// f1(A(1)); // 匿名对象
// cout << "--------------------" << endl;
//
// return 0;
//}
A f2()
{
// NRVO(命名返回值优化)
//A aa;
//cout << &aa << endl;
//return aa; // aa在返回时就销毁了,返回的是aa的拷贝
// UPVO(未命名返回优化)
return A(1); // 匿名对象
}
int main()
{
// 传返回值
A aa1 = f2();
// 不优化,整个过程:
// 1、f2函数定义对象aa,调用构造函数
// 2、aa拷贝构造给临时对象,调用拷贝构造
// 3、aa拷贝后f2函数结束,调用析构函数,析构aa
// 4、临时对象拷贝构造给aa1,调用拷贝构造
// 5、临时对象拷贝后,声明周期也结束了,调用析构函数析构
// 优化后:
// 可以理解为aa是aa1的引用,aa的改变就会影响aa1
cout << &aa1 << endl; // aa和aa1地址相同
return 0;
}
结语
如有不足或改进之处,欢迎大家在评论区积极讨论,后续我也会持续更新C++相关的知识。文章制作不易,如果文章对你有帮助,就点赞收藏关注支持一下作者吧,让我们一起努力,共同进步!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。