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.初始化列表
#include<iostream>
using namespace std;
#include<assert.h>
class Date
{
public:
Date (int year, int month, int day,int& i)
: _year(year)//初始化
, _month(month)
,_refi(i)
, _day(day)
{
_day=9;//赋值
}
void Print()
{
cout << "Print" << endl;
}
private:
int _year;
int _month;//声明
int _day;
const int _a = 1; 必须定义时初始化
int& _refi;
};
int main()
{
int n = 0;
Date d1(2024,2, 13,n);
cout << n;
return 0;
}
我们通过调试观察
1.编译器对类的成员初始化时,是根据声明的顺序初始化的,即便初始化列表的顺序和声明的顺序不一样。成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
2.编译器走完初始化列表的所有初始化后才走函数体,下面可以观察到,_day在定义对象时传参为13,走完_day的初始化后按照声明顺序初始化_a,_refi,后才进入函数体,进入函数体后不是给_day初始化,初始化列表已经给每个成员都初始化了,进入函数体是进行赋值。
进入函数体给_day赋值,最后_day的值是9不是传参给的13
3.C++11支持声明给缺省值,这个缺省值是给初始化列表的。如果初始化列表给值了就不会用这个缺省值,缺省值相当于备胎。
3.explicit关键字
一个表达式,连续的步骤里面,连续的构造会被合并,拷贝构造编译器会直接调用构造。
多个对比
int main()
{
A aa1;
f1(aa1);
cout << "------__--------" << endl;
f1(A(1));
cout << "------__--------" << endl;
A aa2(3);
cout << "------__--------" << endl;
f1(2);
cout << "------__--------" << endl;
A()=3;//第一次构造是3隐式类型转换调用构造函数构造一个临时A类对象
//第二次构造是构造匿名对象
//第三次是赋值元素运算符重载
cout << "------__--------" << endl;
A a3 = 3;//3隐式类型转换调用构造函数构造一个临时对象
//构造一个新对象a3
//赋值运算符重载
//但是这里连续构造发生了优化,编译器优化成只调用一次构造
cout << "------__--------" << endl;
// const引用会延长匿名对象声明周期
// ref出了作用域,匿名对象就销毁了
const A& ref = A();
//引用不需要调用拷贝构造
cout << "------__--------" << endl;
A aa3;
cout << "------__--------" << endl;
return 0;
}
1.构造函数只有一个参数
class A
{
public:
A(int i)
:_a(i)
{
cout << "A(int i)" << endl;
}
A(const A& _aa)
{
cout << "A(const A& _aa" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A a1(1);
cout << "----------" << endl;
A a2 = 2;
return 0;
}
这里我们可以观察到,编译器对a1对象调用一次构造函数,析构函数。按照常理,对于第二种,编译器用2调用A的构造函数生成一个临时对象,临时对象拷贝构造给a2。但是实际上编译器只调用了一次构造函数和析构函数,并没有调用拷贝构造函数。
编译器优化
解答:单参数构造函数的隐式类型转换用2调用A的构造函数生成一个临时对象,(由于强制类型转换,2先被A构造函数构造成A对象)再用这个对象去拷贝构造aa2。编译器会再优化,优化用2直接构造。一个表达式,连续的步骤里面,连续的构造会被合并。
2.多个参数
class B
{
public:
B(int b1, int b2)
:b1(b1)
,b2(b2)
{
cout << "B(int b1,int b2,)" << endl;
}
B(const B& _bb)
{
cout << "B(const B& _bb)" << endl;
}
~B()
{
cout << "B~()" << endl;
}
private:
int b1 =9;
int b2 = 5;
};
int main()
{
/*A a1(1);
cout << "----------" << endl;
A a2 = 2;*/
B b(2, 3);
cout << "----------" << endl;
B bb = { 2,3 };
return 0;
}
与上面的分析同理,C++11 支持多参数的隐式类型转换
3.全缺省类型
class B
{
public:
B(int b1=1, int b2=2)
:b1(b1)
,b2(b2)
{
cout << "B(int b1,int b2,)" << endl;
}
B(const B& _bb)
{
cout << "B(const B& _bb)" << endl;
}
~B()
{
cout << "B~()" << endl;
}
private:
int b1 ;
int b2 ;
};
int main()
{
/*A a1(1);
cout << "----------" << endl;
A a2 = 2;*/
B b(1);
cout << "----------" << endl;
B bb = { };
return 0;
}
explicit修饰构造函数,禁止类型转换



4.static成员
概念
特性
class A
{
public:
A()
{
cout << "A()" << endl;
++n;
++m;
}
A(const A& t)
{
++n;
++m;
}
~A()
{
--m;
}
// 静态成员函数的特点:没有this指针
static int GetM()
{
return m;
}
static void Print()
{
x++; 不能访问非静态,因为没有this
cout << m <<" " << n << endl;
}
private:
// 静态成员变量属于所有A对象,属于整个类
// 声明
// 累积创建了多少个对象
static int n;
// 正在使用的还有多少个对象
static int m;
int x = 0;
};
// 定义
int A::n = 0;
int A::m = 0;
int main()
{
A();
A();
A::Print();
A::Print();
A aa1;
aa1.Print();
return 0;
}

C++中静态数据成员
在类内数据成员的声明前加上static关键字,该数据成员就是类内的静态数据成员。其特点如下:
1.静态数据成员存储在全局数据区,静态数据成员在定义时分配存储空间,所以不能在类声明中定义
2.静态数据成员是类的成员,无论定义了多少个类的对象,静态数据成员的拷贝只有一个,且对该类的所有对象可见。也就是说任一对象都可以对静态数据成员进行操作。而对于非静态数据成员,每个对象都有自己的一份拷贝。
3.由于上面的原因,静态数据成员不属于任何对象,在没有类的实例时其作用域就可见,在没有任何对象时,就可以进行操作
4.和普通数据成员一样,静态数据成员也遵从public, protected, private
访问规则
5.静态数据成员的初始化格式:<数据类型><类名>::<静态数据成员名>=<值>
6.类的静态数据成员有两种访问方式:<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
同全局变量相比,使用静态数据成员有两个优势:
1.静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性
2.可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能
静态成员函数
与静态数据成员类似,静态成员函数属于整个类,而不是某一个对象,其特性如下:
1.静态成员函数没有this指针,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数
2.出现在类体外的函数定义不能指定关键字static
3.非静态成员函数可以任意地访问静态成员函数和静态数据成员
此段的原文链接:https://blog.youkuaiyun.com/guotianqing/article/details/79828100
5. 友元
友元函数

class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
正确为cout<<d1 此时为d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
ostream& operator<<(ostream& _cout)
此时cout在右侧,按照规律cout传参进隐藏的this指针应该在右侧
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
在类中的成员函数左侧都有隐含的this指针,当把cout的顺序改为 d1<<cout时可以正常输出运行,但是此时的意义为把cout流中被插进d1,与cout>>d1,把d1差劲cout流中不同,不符合常规,所以这里应把流的运算符重载函数写到类外面,但是在类外面不能放问类的私有成员,将不能正常编译运行,这里引入友元
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
说明:
友元类
class Time
{
friend class Date;
声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
6. 内部类
概念:
特性:
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}
7.匿名对象
在某些时候,我们可以不给对象命名就调用构造函数,析构函数,赋值运算符重载,但是匿名对象的生命周期是有限的。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
void Print() const
{
cout <<"Print->" << _a << endl;
}
private:
int _a = 0;
};
void f1( const A& aa )
{
aa.Print();
}
int main()
{
A aa1;
f1(aa1);
f1(A(1));
f1(2);
A();//匿名对象的生命周期只在这一行
cout << "------" << endl;
A () = 3;
// const引用会延长匿名对象声明周期
// ref出了作用域,匿名对象就销毁了
//const A& ref = A(); //引用不需要调用拷贝构造
//A aa2;
return 0;
}
const引用会延长匿名对象的生命周期
可以理解成对象有了别名,就有了名字,当这个名字作用域结束了匿名对象的生命周期才结束,也就是匿名对象跟着这个名字走。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
void Print() const
{
cout <<"Print->" << _a << endl;
}
private:
int _a = 0;
};
void f1( const A& aa )
{
aa.Print();
}
int main()
{
f1(A(1));
cout << "------__--------" << endl;
f1(2);
cout << "------__--------" << endl;
A();//匿名对象的生命周期只在这一行
cout << "------__--------" << endl;
A () = 3;
cout << "------__--------" << endl;
// const引用会延长匿名对象声明周期
// ref出了作用域,匿名对象就销毁了
const A& ref = A();
//引用不需要调用拷贝构造
cout << "------__--------" << endl;
A aa2;
return 0;
}
我们可以观察到,在const引用后匿名对象并没有在这一行调用完构造函数就调用析构函数,而是对象aa2的生命周期结束后,在程序的末尾别名ref的作用区域结束了才调用析构函数,说明const引用延长了匿名对象的生命周期