目录
1. 再谈构造函数
class Date
{
public:
Date(int year;int month;int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
在类中,成员变量第一次是以声明的形式出现。在我们之前讲过,构造函数的作用并不是构造一个对象,而是初始化对象。那么像构造函数里的,他是初始化吗,显然不是的,每一次调用我们都可以赋给他不同的值,而初始化只会初始化一次。
Date(int year;int month;int day)
{
_year=year;
_month=month;
_day=day;
}
1.1 初始化成员列表
那他是在哪里初始化的呢,其实是隐藏了一个初始化列表。这个初始化列表是对象的每个成员变量初始化的地方。
第一个我们直接在函数体内赋值,其实也有初始化列表,只不过没有显示且初始化的是随机值。
语法格式如下:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
1.2 函数体内赋值和初始化成员列表区别
这两种有什么区别呢?其实就像下面这两种写法。
int a;
a=10;
int a=10;
1.3 初始化成员列表的作用
1.3.1 const成员变量
假如类里面声明了一个const成员变量
//错误的写法
class Date
{
public:
Date(int year, int month, int day)
{
_year=year;
_month = month;
_day = day;
x = 10;
}
private:
int _year;
int _month;
int _day;
const int x;
};
直接会有报错。

其实也不难想,声明了一个常量,在定义的时候需要对它进行初始化,在函数里面已经是赋值了。这时候就需要初始化成员列表。
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
, x(10)
{}
1.3.2 引用成员变量
在之前学习引用的时候,我们知道在定义的时候也需要初始化,所以和const成员变量一样,定义的时候就要初始化。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
, x(10)
,ret(day)
//让他成为day的引用,ret改变day改变
{}
private:
int _year;
int _month;
int _day;
const int x;
int& ret;
};
1.3.3 自定义类型成员变量
//错误的例子
class Time
{
public:
Time(int hour, int minute, int seconds)
:_hour(hour)
, _minute(minute)
, _seconds(seconds)
{}
private:
int _hour;
int _minute;
int _seconds;
};
class Date
{
public:
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
, x(10)
,ret(day)
//让他成为day的引用,ret改变day改变
{}
private:
int _year;
int _month;
int _day;
const int x;
int& ret;
Time _t;
};
假如Date类里面有个自定义Time类型,定义Date对象时,会调用Time类里的默认构造函数,假如Time类里没有默认构造函数,那么也需要我们在初始化列表里初始化。

//正确做法
Date(int year, int month, int day,int hour,int minute,int seconds)
: _year(year)
, _month(month)
, _day(day)
: x(10)
, ret(day)
, _t(hour,minute,seconds)
{}
其实初始化列表,函数体内赋值也可以混写,不过有点四不像,假如需要初始化列表就全用初始化列表,写函数体内赋值就都写函数体内赋值。
自定义类型有默认构造函数时,可以使用初始化成员列表,也可以不使用。
不过建议有默认构造函数也使用初始化成员列表。
当我们不使用初始化成员列表时
Date(int year, int month, int day, int hour, int minute, int senconds)
{
_year = year;
_month = month;
_day = day;
Time t(hour, minute, senconds);
_t = t;
}
隐藏的初始化成员列表会调用time的默认构造函数,t对象调用构造函数,_t=t又调用了一次赋值重载函数。
假如写入初始化成员列表中:
Date(int year, int month, int day,int hour,int minute,int seconds)
: _t(hour,minute,seconds)
,_year (year)
,_month (month)
,_day (day)
{}
只调用一次构造函数。
反正能使用初始化列表就用初始化列表。
1.4 初始化列表的顺序

列表初始化的顺序必须和类中声明的顺序保持一致,不然就会产生错误。比如这里实际先初始化的是a2,由于a1是随机值,所以a2就是随机值,a1是1。
输出结果就是1,随机值
2. 匿名对象
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
A a1(1);//在main函数域
A(1);//匿名对象生命周期在这一行
return 0;
}
匿名对象有什么用呢。
比如像leetcode上,我们使用c++,进行oj的时候会创建一个solution类,测试的时候就直接sloution().方法(参数)。这就是一个匿名类的应用场景,不需要专门再去创建一个对象去调用方法。
当形参引用匿名对象时,形参必须为const.
3. explicit关键字
延续上面的代码来继续讲解,这里我们看到给a1对象赋值了一个3,都不是一个类型竟然成功了,输出3。其实这里是发生了隐式类型转换。
3会转换成一个匿名对象,然后赋值重载给a1。

3会转换成一个匿名对象,然后拷贝构造给a2。但是编译器将这两个过程优化合并直接调用构造函数

可以类比引用,中间发生的隐式类型转换
int i=10;
const double& a=i;
怎么来证明它发生隐式类型转换了呢,c++专门提供了个关键字explicit来阻止隐式类型转换,我们在构造函数前面加上这个关键字,如果刚才那条语句不成功,就说明那里准备要发生隐式类型转换,只是被explicit阻止了。

看上去没什么差别,实际上这种隐式类型并不是多此一举。比如说用匿名sloution类调用函数。
第一种是先构造一个str对象,然后做参数传入函数,拷贝构造

第二种用string类构造一个匿名对象,传入函数,拷贝构造

第三种直接传进去,然后让他自己进行隐式类型转换,成为匿名对象然后拷贝构造。只不过编译器直接优化让他只调用构造函数

第三种看上去更舒服。
注意
假如函数的形参是引用匿名对象的话,则必须给形参加上const。

因为"1234",隐式类型转换为临时匿名对象,具有常性。需要加const。整个生命周期在这个函数内。
我又想到了那么在main函数内,假如我引用一个匿名对象呢

可以看到这一行结束,并没有调用析构函数来结束他的生命。而是等main函数走完,才进行清理资源。

等等,我又发现为啥形参需要把引用写成const,我刚才在main里没有写const还调用成功了。
那是因为你调用的时候,直接传了"1234",他还要隐式类型转换成临时匿名对象。当然要加const。
你在main函数里直接写的是匿名对象Date(3)不需要隐式类型转换就不用加const了。假如你写一个需要隐式类型转换的

就需要加const了
4. 静态成员变量
面试题:怎么计算一个类有多少个对象。
先来看看静态成员变量,我们可以把成员变量加上static,他就不属于某个对象,而是属于整个类。在类内声明,而且需要在类外定义。
用sizeof计算一下,发现他是个空类,占一个字节(表示类存在)

在构造函数和拷贝构造里面++_b,由于_b是属于类的,所以调用了多少次构造和拷贝构造都会被计数器计数。面试题就解决了。

假如成员变量是公有的,类也可以调用,那么对象也可以调用,但是这样(b3._b)并不代表解引用(去对象中找他的变量)而是去对象(b3)的类(B)中去寻找静态变量_b。
私有的只能用一个成员函数,来输出类静态变量
5. 静态成员函数
相比于非静态成员函数来说,静态成员函数没有this指针.
可以思考两个问题,时刻记住静态成员函数没有this指针就好。
- 静态成员函数可以调用非静态成员函数吗?
- 非静态成员函数可以调用类的静态成员函数吗?
5.1 静态成员总结
- 静态成员为所有类对象所共享,不属于某个具体的实例
- 静态成员变量必须在类外定义,定义时不添加static关键字
- 在成员函数中类静态成员可用类名::静态成员或者对象.静态成员来访问,在类外也可以(不是私有的话)
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
6. c++11
声明的时候给缺省值。

c++98中,编译器默认生成的构造函数,针对内置类型没有处理,会对自定义类型调用默认构造函数。在c++11中,在声明的时候给缺省值(很像定义但他不是定义!!!)
7. 友元
7.1友元函数
我们用cout,或者cin想输出或输入一个自定义类型的对象,需要重载操作符
class Date
{
public:
Date(int year=1900, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& out)
{
//this->_year,this->_month,this->day
out<<_year<< _month <<_day<<endl;
return out;
}
private:
int _year;
int _month;
int _day;
};
当我们调用他的时候,有两种方法:
Date d1;
d1.operator<<(cout);
d1 << cout;
但是第二种看起来怪怪的,在平常我们输出内置类型的时候通常是
int i=10;
cout<<i;
但对于输出对象我们只能把d1放在左边,因为作为成员函数,指向对象d1的隐含的this指针始终是作为第一个参数,输出流作为第二个参数,所以就是这样的形式 d1<<cout;
那交换这两个参数的位置呢,交换不了,this永远是第一个,这样写只是相当于给他多加了一个参数。

那我们不把他作为成员函数。放在外面,没有this指针。可以交换两个参数的位置。但是呢成员变量是私有的外面有访问不了。

所以c++又给出了友元friend。在类里声明这个函数为friend。代表他可以访问这个类的私有成员和保护成员。
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year=1900, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
{}
//ostream& operator<<(ostream& out)
//{
// //this->_year,this->_month,this->day
// out<<_year<< _month <<_day<<endl;
// return out;
//}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year <<"-"<< d._month <<"-"<< d._day<<endl;
return out;
}
int main()
{
Date d1;
Date d2(2000, 5, 6);
cout << d1 << d2 << endl;
return 0;
}
现在的场景是,Date类里有个Time类型的成员变量,重载了对象的输入输出,作为Date的友元函数,可是现在输入输出只有Date的内置类型,怎么才能输出Time成员变量的几个参数呢
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour,int minute,int seconds)
:_hour(hour)
, _minute(minute)
, _seconds(seconds)
{}
private:
int _hour;
int _minute;
int _seconds;
};
class Date
{
//重载输入输出
friend istream& operator>>(istream& in, Date& d);
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year, int month, int day)
:_year( year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator>>(istream& in,Date& d)
{
cout << "请输入 年 月 日" << endl;
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-"<<d._month <<"-"<< d._day << endl;
return out;
}
int main()
{
Date d1(2000, 1, 1);
cin >> d1;
cout << d1;
return 0;
}
这样访问是不被允许的,所以我们在将这个函数变成Time的友元函数,就可以直接访问了
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-"<<d._month <<"-"<< d._day << endl;
out << d._t._hour << "-" << d._t._minute << "-" << d._t._seconds << endl;
return out;
}
将它加在time类当中
friend ostream& operator<<(ostream& out, const Date& d);
但是由于Date类型在Time类后面定义,你把声明写在前面的时候形参有个const Date& d,他不认识,所以在最前面在加一个Date声明。就完成了。
#include<iostream>
using namespace std;
class Date;
class Time
{
friend ostream& operator<<(ostream& out, const Date& d);
public:
Time(int hour,int minute,int seconds)
:_hour(hour)
, _minute(minute)
, _seconds(seconds)
{}
private:
int _hour;
int _minute;
int _seconds;
};
class Date
{
//重载输入输出
friend istream& operator>>(istream& in, Date& d);
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year, int month, int day,int time,int minute,int seconds)
:_year( year)
, _month(month)
, _day(day)
, _t(time,minute,seconds)
{}
private:
int _year;
int _month;
int _day;
Time _t;
};
istream& operator>>(istream& in,Date& d)
{
cout << "请输入 年 月 日" << endl;
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "-"<<d._month <<"-"<< d._day << endl;
out << d._t._hour << "-" << d._t._minute << "-" << d._t._seconds << endl;
return out;
}
int main()
{
Date d1(2000, 1, 1,23,14,13);
/*cin >> d1;*/
cout << d1;
return 0;
}
这也正说明了他可以是多个类的友元函数。
友元函数的特点:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用和原理相同
7.2 友元类
在上面代码的基础上,假如我们想要在这个Date类中,直接用自定义类型_t来访问它的成员变量是不行的,因为_hour等是私有的无法访问
Date(int year, int month, int day,int hour,int minute,int seconds)
:_year( year)
, _month(month)
, _day(day)
, _t(hour,minute,seconds)
{
_t._hour=30;//报错
}
所以我们在Time类中声明Date是自己的友元( friend Date;)
可以访问Time的私有。(Date类中_t._hour=30;不会报错了)
需要注意友元是单向的且不具有传递性。(其实一定程度也破坏了封装性,尽量不要使用友元)
8. 内部类
class A
{
private:
static int _a;
int _c;
public:
class B
{
public:
void fool(const A& a)
{
cout<<_a;
cout << a._c;
}
};
};
int A::_a = 1;
B就是一个内部类。而且B天生就是A的友元。定义B的对象可以用
A::B b;来定义
9. 补充(const修饰成员函数以及最后两个默认生成的成员函数)
9.1 const修饰成员函数
#include<iostream>
using namespace std;
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 d1(2000, 8, 1);
d1.print();
return 0;
}
当d1调用print时,print函数有着隐含的this指针,指针类型为Date* ,指向d1。
现在我们让d1对象作为只读即前面加const。

会报错,不难想,因为print函数里隐含的this指针为Date*,而我们此时传入的&d1,为const Date*,权限被放大,不被允许。
所以我们在print后面加上const,修饰this指针,表示所指向的对象不能被修改,这个时候就可以传递了,因为参数类型相同。
而且Date*,也可以被传递,因为权限缩小是可以的。
.....
void print()const
{
cout << _year << "-"<< _month <<"-"<< _day << endl;
}
....
int main()
{
const Date d1(2000, 8, 1);
d1.print();
Date d2(d1);
d2.print();
return 0;
}
无语法错误,正常输出

有4道练习题可以练习一下。
- const对象可以调用非const成员函数吗?
- 非const对象可以调用const成员函数吗?
- const成员函数内可以调用其它的非const成员函数吗?
- 非const成员函数内可以调用其它的const成员函数吗?
8.2 取地址与const取地址运算符重载
对对象进行取地址,其实也是重载了运算符&,成员函数内部返回this指针就好
Date* operator&()
{
cout << "Date* operator&()" << endl;
return this;
}
const Date* operator&()const
{
cout << "const Date* operator&()const" << endl;
return this;
}
....
int main()
{
const Date d1(2000, 8, 1);
Date d2(d1);
cout<<&d2<<endl;
cout<<&d1<<endl;
return 0;
}
只是需要注意的是,刚才提到,假如对象为const类型,第一个成员函数没有用const修饰this指针(指针指向的对象不变),第一个函数传不进去,所以需要为const对象写出第二个函数。

当我们不写时,它们会自动生成,与其他几个默认生成的构造函数不同,它几乎所有场景都够用了,但是什么时候我们需要重新编写它。
有这种场景,我们不想让别人返回普通对象的地址,const对象才能取到地址.
我们可以只重写普通对象的取地址重载函数,让他返回nullptr。另外一个可以用编译器默认生成的。
Date* operator&()
{
cout << "Date* operator&()" << endl;
return nullptr;
}
只返回了const对象的地址。

那假如我只想取普通对象地址,不想取const对象地址,像刚才一样只重写const修饰的取地址运算符重载函数,让他返回空。
却突然发现两个都返回了nullptr

其实也可以考虑到,因为普通对象的this指针为Date*,函数形参的this指针为const Date*,权限缩小,也可以传递,两个对象都可以调用const修饰的取地址运算符重载函数。
所以当我们只想取普通对象的地址,不想取const对象的地址,我们需要同时重新编写这两个成员函数,让他自己调用自己的。
Date* operator&()
{
cout << "Date* operator&()" << endl;
return this;
}
const Date* operator&()const
{
cout << "const Date* operator&()const" << endl;
return nullptr;
}

本文详细介绍了C++中构造函数、初始化列表、成员变量初始化、const成员变量、引用成员变量、自定义类型成员变量的初始化方式。强调了初始化列表的重要性,如在初始化const和引用成员时的必要性。此外,还讨论了匿名对象、explicit关键字、静态成员变量和函数、友元以及C++11特性。文章通过实例展示了如何避免隐式类型转换,以及如何使用友元函数访问私有成员。最后,探讨了const成员函数和取地址运算符重载的使用规则及其注意事项。
&spm=1001.2101.3001.5002&articleId=114436488&d=1&t=3&u=d03e3aa721844b28b257c14e468c10cd)
1986





