文章目录

空类
class Person {
};
sizeof(Person) //1
空类的大小为1.因为实例化类后为每个对象分配内存空间,若要是0空间,对象会被分配到同一块空间上,不能区分开来。编译器至少给空类分配一字节。
空类里有什么默认的成员函数?
编译器通常会声明常用的四个有缺省的无参构造,析构,拷贝构造和赋值运算符重载 + 两个取地址操作符重载。在真正被需要的时候编译器才会生成函数定义。
C++11增加了移动构造和移动拷贝构造。
构造函数
什么是构造函数?
函数名和类名相同,创建对象时编译器自动调用,在对象生命周期内只调用一次,没有返回值。是为了更好地初始化资源应运而生的。
class Date{
public:
Date(int month){
_month = month;
cout<<_month<<endl;
}
private:
int _day;
int _month;
int _year;
}
void TestOnece(){
Date d(12);
}
//执行TEst函数后打印了一次,证明对象的生命周期内只调用了一次构造函数
无参构造函数和全缺省的构造函数只能有一个,会造成二义性
Date(){
}
Date(int day = 1){
_day = day;
}
初始化变量在创建完成对象创建之前,所以此时this指针并不知道每个成员变量的大小,也就不能用如下的形式使用成员列表进行赋值。
//错误的
Date(int day)
:this->_day(day),
{
_day = day;
}
//提倡的成员列表赋值
Date(int day, int month, int year)
:_day(day), _month(month), _year(year){
}
编译器初始化成员列表的顺序跟成员变量的声明顺序相同,尽管参数列表中的顺序可以自己写,但编译器可不会按你的顺序进行赋值。
必须用初始化列表进行初始化的成员:
const成员类型,继承体系中父类的构造函数。
如果用内置类型给一个类类型赋值会怎么样?
编译器会寻找一个参数的构造函数,进行隐式类型转换。这样的做法显然有点危险,如果要拒绝自动的隐式转换,在构造函数前用explicit 修饰。
默认构造函数
什么是默认构造函数?
不带形参的构造函数,或者全缺省的构造函数。
https://www.cnblogs.com/QG-whz/p/4676481.html
什么时候会自动生成默认构造函数?
惟有默认构造函数被编译器需要的时候编译器才会合成默认构造函数,并不是没有自定义的构造函数就一定生成。
被需要的时机:
- 含有类类型的成员变量,且该类有自己的默认构造函数
- 继承体系中,派生类需要调用基类构造
- 带有虚表或者偏移表的类
拷贝构造函数
什么是拷贝构造函数?有什么用?
只有一个参数的构造函数,且参数是本类类型对象的引用.用于使用旧对象创建新对象。
class Date{
Date(const Date& d)
:_day(d._day), _month(d._month), _year(d._year)
{
}
}
Date d1;
Date d2(d1);
必须要用引用参数,如果是值传递的话会创建d临时变量,而创建d也会调用拷贝构造,形成死循环。
默认拷贝构造函数
和默认构造函数相似,被需要时编译器才会创建默认拷贝构造,否则是通过汇编命令完成复制动作的。
时机:需要用到自身类创建自身类的时候。
class Time{
//Time类的默认构造函数
Time(){
cout<<"构造";
}
//Time类的拷贝构造
Time(const Time& t){
}
}
class Date{
//编译器生成的默认拷贝构造函数的形式
Date(const Date& d)
:_day(d._day), _month(d._month), _year(d._year), _t(d._t)
{
}
private:
int _day;
int _month;
int _year;
Time _t;
}
析构函数
没有参数,在对象被销毁时,编译器自动调用。析构函数不是销毁对象的,是销毁对象前的清理工作。
什么场景下有必要写析构函数?
在对象里有动态分配内存,析构时需要把动态分配的内存free掉。
class Array{
public:
Array()
:_array(NULL)
{
_array = (int*)malloc(sizeof(int));
}
~Array(){
if(_array){
free(_array);
}
}
private:
int* _array;
}
关于构造和析构的顺序
在继承体系中,先调用基类构造,然后调用成员对象的构造函数,析构与之完全相反。
在函数调用过程中:
Date func(Date date)
{
Date tmp(date);
return tmp;
}
void fun()
{
Date date;
date = func(date);
}
执行结果:
构造函数00BCFDB7
复制构造00BCFCC0
复制构造00BCFC9F
复制构造00BCFCDF
析构00BCFC9F
析构00BCFCC0
析构00BCFCDF
析构00BCFDB7
基本上遵循后构造先析构的原则,在退出函数作用域时比较特殊,作为返回值的临时变量在出函数赋值后销毁。
特殊的,无名类作为返回值时,生命周期在赋值后结束。
运算符重载
只是为了把函数调用换成更简洁的符号操作。
Date operator+(int days){
Date tmp(*this);
tmp._day += days;
return tmp;
}
//连续赋值
Date& operator=(const Date& d){
//避免自我赋值
if(this != &d){
_day = d._day;
}
return *this;
}
Date d;
Date d2;
d2 = d + 1;
//等价
d2 = d.operator+(10);
+号的运算符重载不能更改左操作数,所以创建了临时变量。
=号需要更改左操作数的值,要注意返回当前对象可以连续赋值。
//前置++运算符重载
Date& operator++() {
_day += 1;
return *this;
}
//后置++
Date operator++(int) {
Date tmp(*this);
_day += 1;
return tmp;
}
前置++,返回当前对象的引用,后置++返回自加前的临时变量再给当前变量+1。
不能重载的四个运算符 . .* :: ?:
map的key_compare()
stl::map 的key如果是自定义结构体,那么默认会使用结构体的operate < 作为key_compare函数。key_compare 函数返回true的含义是this的值应该在rhs的前边(排序位置)。
如果一个struct 有两个值A,B,但 oprerate < 里只比较一个值A,那么在find()的时候只要值A相等那么find就认为找到这个值了。(我们本意是排序时候不计算B,但相等的时候要计算B啊)
但find的实现其实是没有调用operate =的,而是等价的概念,只要!(A<B) && !(B<A) 则认为等价。
在排序里,相等的概念是没有用的,只要等价,我的位置顺序就是相同的。
那能不能让find 使用相等 的概念呢?可以把,先确定等价的元素范围,逐个判断相等否?
友元函数的出现
如果想要重载类的<<运算符,那么大概想在类里写一个这种的
ostream& operator<<(ostream& cout) {
cout << _day << endl;
return cout;
}
但是这样调用<<必须是date << cout
或者 date.<<(cout)
这种很不正常的调用形式。
如果在类域外定义一个函数是不是能解决参数顺序的问题
ostream& operator<<(ostream& cout, Date& date) {
cout << date._day << endl;
return cout;
}
这样还有一个问题,类外的函数不能访问私有成员变量,也就不能打印_day.
此时友元函数诞生了,friend函数不是成员函数,却可以访问其私有成员。
friend ostream& operator<<(ostream& cout, Date& date);
friend函数可以是多个类的朋友,用法和普通函数相同——没有隐藏的this指针。
friend类:在Date类里想要访问Time类的成员,必须先告诉Time有一个Date朋友。friend类是单向朋友关系,Date可以访问Time,Time却不能访问Date。
*缺省参数只在声明写一次,定义就不能缺省了
优点:在实现类之间数据共享时,减少系统开销,提高效率
缺点:破坏类的封装性
static修饰类成员
使用场景:如果想要记录类创建的对象个数,普通的成员变量不能累计,需要一个全局变量。静态成员变量就可以做到各对象共享,因为同在进程的静态数据区。
静态成员在类里声明,在类外进行初始化。
static声明成员函数是属于类而不属于对象(内存池),为类进行服务和任何对象没有联系,因此没有this指针。
如何保证static对象只有第一次调用构造函数?
单例模式。static创建对象过程,检查是否第一次创建,如果没创建就调用一次构造函数。内部用一个静态全局变量记录是否创建过。
static修饰全局变量和函数
全局变量和函数具有外部连接属性,在多文件编译时具有全局可见性。static修饰后,改变为内部链接属性,仅在本文件内可见。
static修饰局部变量,改变其生命周期,从栈上的变量改变为静态区变量。但没有改变作用域。
C++中的const
为什么使用const?
在使用指针或者引用做读操作时候,通常不希望更改所指向的值,加上const使代码易于维护。
const修饰常量
在c语言中const只是从编译器的语法层面限制变量不能被修改,在c++中做到了真正的常量,常量值在编译期间已知,并在编译期间对所有常量进行替换,有类型检查。
const修饰的常量在哪里存放?
分别测试了局部变量和全局的,发现局部变量总会在栈上开辟空间,在普通的数据栈中。全局变量就比较特殊了,如果在代码其他地方没有对变量&取地址,编译器不为常量开辟空间,在编译期间得到常量值,相反如果后文有对常量&取地址,它是分配在只读数据区的。
const int a = 1;
const int b = 2;
void fun()
{
const int c = 3;
printf("%p", &c);
printf("%p", &a);
printf("%d", b);
}
const修饰类成员!
const修饰成员变量,只能在构造函数的初始化列表给值,否则构造函数会报错。
const类型的成员函数,本质是修饰成员函数隐藏的this指针!
const Date * const this
本来this指针是不可更改指向的指针,const再修饰后也不能更改其指向的值了。因此const修饰的成员函数不能修改成员变量,但是可以修改静态成员变量(不通过this调用)。
如果强行想修改,关键字 mutable 使成员变量可以被const成员函数修改。
const对象可以调用非const成员函数和const成员函数吗?
const对象只能访问const成员函数。因为const对象表示其不可改变,而非const成员函数可能在内部改变了对象,所以不能调用
而非const对象既能访问const成员函数,也能访问非const成员函数,因为非const对象表示其可以改变。
const成员函数内可以调用其它的const成员函数和非const成员函数吗?
由于const修饰this指针的原因,导致其对象内容不可更改,调用const成员权限相同所以可以,调用非const权限放大,不行
内联函数
宏的几个缺点 :https://blog.youkuaiyun.com/hanzheng6602/article/details/79038014#函数和宏的对比
内联函数的特性
宏在预处理阶段简单替换,而内联函数在编译期间将内联函数调用的地方展开,避免函数压栈开销。对于频繁调用的函数声明为内联比较划算。
inline关键字仅仅是对编译器有建议作用,就像register。
在类体内定义的成员函数,编译器大概率把函数修饰为内联。
在c++中强烈建议用const常量代替宏常量,用内联函数代替宏函数。既提高了运行效率又有参数检测。