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;
}
那他是在哪里初始化的呢,其实是隐藏了一个初始化列表。这个初始化列表是对象的每个成员变量初始化的地方。
第一个我们直接在函数体内赋值,其实也有初始化列表,只不过没有显示且初始化的是随机值。
语法格式如下:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
这两种有什么区别呢?其实就像下面这两种写法。
int a;
a=10;
int a=10;
假如类里面声明了一个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)
{
}
在之前学习引用的时候,我们知道在定义的时候也需要初始化,所以和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;
};
//错误的例子
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)
{
}
只调用一次构造函数。
反正能使用初始化列表就用初始化列表。
列表初始化的顺序必须和类中声明的顺序保持一致,不然就会产生错误。比如这里实际先初始化的是a2,由于a1是随机值,所以a2就是随机值,a1是1。
输出结果就是1,随机值
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().方法(参数)
。这就是一个匿名类的应用场景,不需要专门再去创建一个对象去调用方法
延续上面的代码来继续讲解,这里我们看到给a1对象赋值了一个3,都不是一个类型竟然成功了,输出3。其实这里是发生了隐式类型转换。
3会转换成一个匿名对象,然后赋值重载给a1。
3会转换成一个匿名对象,然后拷贝构造给a2。但是编译器将这两个过程优化合并直接调用构造函数
可以类比引用,中间发生的隐式类型转换
int i=10;
const double& a=i;
怎么来证明它发生饮食类型转换了呢,c++专门提供了个关键字explicit来阻止隐式类型转换,我们在构造函数前面加上这个关键字,如果刚才那条语句不成功,就说明那里准备要发生隐式类型转换,只是被explicit阻止了。
看上去没什么差别,实际上这种隐式类型并不是多此一举。比如说用匿名sloution类调用函数。
第一种是先构造一个str对象,然后做参数传入函数,拷贝构造
第二种用string类构造一个匿名对象,传入函数,拷贝构造
第三种直接传进去,然后让他自己进行隐式类型转换,成为匿名对象然后拷贝构造。只不过编译器直接优化让他只调用构造函数
第三种看上去更舒服。
面试题:怎么计算一个类有多少个对象。
先来看看静态成员变量,我们可以把成员变量加上static,他就不属于某个对象,而是属于整个类。在类内声明,而且需要在类外定义。
用sizeof计算一下,发现他是个空类,占一个字节(表示类存在)
在构造函数和拷贝构造里面++_b,由于_b是属于类的,所以调用了多少次构造和拷贝构造都会被计数器计数。面试题就解决了。
假如成员变量是公有的,类也可以调用,那么对象也可以调用,但是这样(b3._b)并不代表解引用(去对象中找他的变量)而是去对象(b3)的类(B)中去寻找静态变量_b。
私有的只能用一个成员函数,来输出类静态变量
相比于非静态成员函数来说,静态成员函数没有this指针.
可以思考两个问题,时刻记住静态成员函数没有this指针就好。
声明的时候给缺省值。
c++98中,编译器默认生成的构造函数,针对内置类型没有处理,会对自定义类型调用默认构造函数。在c++11中,在声明的时候给缺省值(很像定义但他不是定义!!!)
我们用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<
那交换这两个参数的位置呢,交换不了,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<
// 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;
}
友元函数的特点: