目录
一、再谈构造
1.初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
并且和构造函数内的赋值可以混着使用
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
{
_day = day;//这里
}
private:
int _year;
int _month;
int _day;
};
初始化列表是每个成员定义的地方
(private:
int _year;
int _month;
int _day;//这个为声明
)
初始化列表的存在主要是为了解决一下三个问题。
1.const对象的赋值
private:
int _year;
int _month;
int _day;
const int a;
const对象是不能放在构造函数里赋值的,因为const对象只能在定义的时候初始化
2.引用的赋值
private:
int _year;
int _month;
int _day;
int & a;
和const对象一样,引用也只能在定义的时候初始化
3.构造一个自定义类型,但是这个自定义类型没有默认构造(即它有其它形式构造函数,但是没有默认构造,我们又未传参的情况,所以我们要在它定义的时候调用带参的构造函数),又或者它有默认构造但是我并不想调用默认构造
总体如下,如果成员是个内置类型,初始化列表就相当于定义和初始化一起做了,但是如果这个成员是自定义类型,那么就相当于传参调用这个自定义类型的构造函数
最后再次强调
初始化列表是每个成员定义的地方,不管写不写,每个成员都要走初始化列表
可以理解为内置类型不处理,自定义类型调用它的构造函数,就是在这步
class A
{
public:
A(int m)
:_a(m),
_b(m)
{}
private:
int _a;
int _b;
};
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
,_x(1)
{}
private:
int _year;
int _month;
int _day;
A _x;
};
2.c++11中,可以在声明的时候给缺省值
并且在声明处的缺省值可以调用函数
这个缺省值实际上就是给初始化列表的!
但是优先级会低一些。
如果初始化列表没有显式给值,那么初始化列表就会使用这个缺省值
(同时初始化列表执行的顺序是按照声明的顺序)
最后走完初始化列表之后,走函数体,就相当于一个赋值的过程
综合以上根据初始化列表的特性,可以用初始化列表我们就用初始化列表,一些特殊的情况下我们可以初始化列表和函数体混着用,比如检查错误,初始化开辟的空间
3.构造的优化现象
class A
{
public:
A(int m)
:_a(m)
{}
private:
int _a;
};
A a2(1);
A a2 = 1;/
两者是等价的 //第二个利用了编译器支持单参数构造函数的隐式类型转换,隐式类型转换生成了一个临时对象
第一个代码是直接传1进行构造,
第二个代码为先使用1进行构造生成临时对象
形成一个临时对象之后,再进行拷贝构造
但是编译器会进行优化,把2优化为直接构造
我们再看,当我们这样
A& a = 1;
这样是不行的,因为临时对象具有常性,不能被引用。
const A& a = 1;(这里a这个引用延长了1转换产生的临时变量的生命周期,一直到a的作用域 结束,1产生的临时变量才结束)
这样子就可以了
单参数的隐式类型转换有它的应用场景,例如一个实现顺序表的类,里面所要存储的是一个单参数的类
这样构造会方便很多
连续的拷贝构造也会优化
本来函数返回前会拷贝构造一个临时对象,然后再对ret1拷贝构造,但是会被编译器优化成直接对ret1拷贝构造
如果return 的是一个匿名对象,或者是一个可以隐式类型转换的数,会被直接优化为构造。
原本 构造 + 拷贝构造临时对象 + 拷贝构造ret1
优化1两次拷贝构造同上,优化为一次拷贝构造。
优化2构造 +拷贝构造,优化为一次构造。
因此可以传匿名对象就尽量传匿名对象,因为不仅方便还可以触发编译器的优化
4.c++11支持多参数的隐式类型转换
5.explicit可以禁止隐式类型转换
只要在构造函数前面加个explicit即可
6.匿名对象
//有名对象,其生命周期在当前局部域
A a(1);
//匿名对象,生命周期只在这一行
A(1);
匿名对象在我们禁用隐式类型转换的时候可以帮助我们简化代码
以及在一些调用中简化代码
就不给他们取名字。
无参的时候使用一个()表示
Solution()就是定义一个匿名对象
类似临时对象,const 也会延长匿名对象的生命周期。
例如 const A& ref = A();
二、static成员
在类的成员前面加上static,那么这个成员就属于整个类,例如
class A
{
public:
private:
static int i;//声明
};
int main()
{
int A::i = 0//定义
return 0;
}
它有如下一些特点
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,所以不能在定义时给它缺省值。
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
4. 类静态成员即可用 类名::静态成员访问,或者 对象.静态成员来访问(但是这仅仅只是突破了类,并不是说这个类静态成员在这个对象里面,因此我们一般使用类名访问),上述这两个办法也受访问限定符限制,仅限于这个静态成员变量为公有,因此我们还可以在类里面写一个函数来访问这些成员。
5. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
(静态成员函数和静态成员变量是配套的,)
一道例题
class sum
{
public:
sum()
{
ret = ret + i;
i++;
}
static int getret()
{
return ret;
}
private:
static int i;
static int ret;
};
int sum::i = 1;
int sum::ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
sum arr[n];
return sum::getret();
}
};
三、
cin是一个istream类型的对象,cout是一个ostream类型的对象
对于各种内置类型的流插入与流提取,可以直接用的原因是这两个对象重载了以不同内置类型为形参的成员函数。
是运算符重载和函数重载的结合
int i = 0;
cout << i;//即 cout.opertaor << (i);
本质上是调用了cout里面的一个成员函数
这是一个日期类
当我们想要重载自定义类型时,可以采用类似的方法
class Date
{
public :
void operator <<(ostream& cout)//一共传入两个参数,this指向日期类,另一个是ostream类型的
//cout
{
out << _year << "/" <<_month << "/"<< _day << endl;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
但是这样有一个缺点那就是我们调用函数时必须使用
d1 << cout;
的形式,因为他们的参数顺序已经被规定好了,因此我们要使用cout << d1;的形式,必须把函数定义在全局。
我们首先想到的是这种方式,但是这种方式是错误的,因为日期类的成员变量都是私有的!
void operator <<(ostream& cout,const Date& d)
{
out << d._year << "/" <<d._month << "/"<< d._day << endl;
}
对于这种情况我们有两种解决方案,
1.在类里面提供getyear(),getmonth(),getday();这三个函数
2.使用友元声明,因为这只是一个声明,所以加在日期类的任意地方都可以
class Date
{
friend void operator <<(ostream& cout,const Date& d);
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
(之所以要重载自定义类型的cout <<是因为printf不能很好地支持自定义函数的输出,printf中%后的字母如%d,%c等等是已经被固定的,当我们将某个成员变量的值从整型改为浮点型,%d的输出就不再适用,但是cout中重载的函数却可以应对这种情况)
最后,我们依然是再进一步,考虑到连续赋值的情况,我们将函数的返回值改为ostream&
ostream& operator <<(ostream& cout,const Date& d)
{
out << d._year << "/" <<d._month << "/"<< d._day << endl;
return cout;
}
这样就可以支持
cout << d1 << d2;
因为这个函数是从左往右执行的,因此cout << d1的返回值应当是ostream类型的 ,即我们将cout返回即可
类比到cin
istream& operator <<(ostream& cin, Date& d);
几乎是一样的,除了const Date& d,变为了Date& d,因为流插入就是要在目标对象中插入值。
流插入以空格和回车作为结束标志
四、友元
1.友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
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;
}
1.友元函数可访问类的私有和保护成员,但不是类的成员函数
2.友元函数不能用const修饰,因为它没有this指针(即后置的那个const)
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4.一个函数可以是多个类的友元函数
5.友元函数的调用与普通函数的调用原理相同
2.友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
1.友元关系是单向的,不具有交换性。
比如下面Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
简而言之,就是一个类要访问另一个类的私有成员,那么这个类就要是另一个类的友元
2.友元关系不能传递
如果B是A的友元,C是B的友元,则不能说明C时A的友元。
3.友元关系不能继承,在继承位置再给大家详细介绍。
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;
};
五、内部类
class A
{
public:
class B
{
private:
int _b;
};
private:
int _a;
};
int main()
{
cout << sizeof(A) endl://4
A aa;
A::B bb;
}
B是A的内部类,但是实际上他们两个是独立的类,只是B类要受A类类域和访问限定符的限制
我们定义一个A类型对象时,算出来的大小是4,因为A里并没有同时产生一个B类型对象
同时,内部类天生是外部类的友元
应用场景就是,一个类专门是为另一个类服务的,就可以用内部类。
上面那道例题优化
class Solution {
class sum
{
public:
sum()
{
ret = ret + i;
i++;
}
};
public:
int Sum_Solution(int n) {
sum arr[n];
return ret;
}
private:
static int i;
static int ret;
};
int Solution:: i = 1;
int Solution:: ret = 0;