C++中结构体不可以有成员函数。
【结构体大小计算回忆】

在64位下,指针大小为8字节。
a1:4,
a2:4
a3:4
指针:8
sum:20 => 24,因为需要成8的倍数。【对齐数】
如下图,缺省和无参不可以同时存在,语法上无参和全缺省可以同时存在,因为构成了函数重载。因为如果创建Date d1;这样有二异性。它不知道调用缺省构造函数还是无参构造函数。




此外注意:创建对象不给参数时,直接用对象名,不加括号。
不是完成对象的销毁,为了完成对象中的一些资源清理工作。比如:对指针在堆上申请的空间做释放。此外,一个类只有一个析构函数。
~Date()
{
//释放指针类资源,比如栈中a指针,会申请一片连续空间。;
free(a);
// 变量清0、该置0的置0
}
编译器生成的默认析构函数和默认构造一样,自定义的类型会去调用它本身的析构函数【就是你类中的类属性,默认析构调用它的析构】。而内置类型就不会自动删除。
虽然C++有缺点,但是也方便。方便就方便在类中的类属性成员变量,会自己直接调用类属性它的类的默认构造参数,直接就做完赋值了,比较方便。
总结:没有资源需要清理,不用自己实现析构函数,其中对于自定义类型会调用该自定义类型的析构,内置类型不做处理。
- this指针的类型: 类类型* const
- 只能在成员函数内部使用
- 对象中不存储this指针,this指针只是个“成员函数”的形参。当对象调用成员函数时,编译器自动把对象地址作为实参给了this形参,this形参实际是第一个成员函数的参数,它是隐藏起来的。所以对象中不存储this指针,且没必要存。
- 【注意:】可以为空,当你不输出或不使用对象成员变量时,显然可以不穿地址,所以this没必要接收。


- 全局的C和static D,都存在静态内存区,在这个静态区内,也得符合栈顺序。
- A是在堆上申请开,和局部静态变量是同级的,A语句早于C语句,所以A先掉构造。然后是C,再D。且A、C都在数据段,这两个析构顺序符合栈特点
最后析构顺序按照构造顺序相反。


- 拷贝构造函数与构造函数构成重载。
- 使用同类型的对象值去拷贝,加const防改变且用别名&防无限递归调用。简言之,参数类型MyClass必须加&
解释2: 对象类型的参数都是临时变量,临时拷贝出来的,编译器自动调用拷贝构造函数。所以如果拷贝构造的参数是类,会无限递归。,所以一定记住:参数类型MyClass必须加&,也就是类的引用。
【总结】:系统会自动生成默认拷贝构造函数,系统会对内置类型和自定义类型都会拷贝处理。但是对两者处理细节不一样,且和构造、析构都不一样。
特点:
拷贝构造使用的代码举例
// 法1
class A1(2,1);
class A2(A1);
// 法2:
class A3 = A1;
// 调用
A3 = A2; 这是在
而最下面那一行,已经有了对象了,那个涉及的是赋值,而不是拷贝构造,且对象类型的右值,编译器会自动做=运算符重载。
【回忆】:
** 插入:【size_t】类型是什么**
size_t:数组下标,无符号整数类型。
无符号不能保存负数,最低存到-1。
- 常函数:成员函数后面加const后称这个函数为常函数。如下:
void Person::show() const- 常函数内不可以修改成员属性。即不可以出现类似如下:
void fun(){ this->a = val}; 这种改变成员变量值的赋值语句绝不可以。- 成员变量声明加mutable,在常函数中依然可以修改。
- 声明和实现时,都需要加const。
【构造函数的初始化列表】
在调用构造函数之前,先会默认做初始化列表操作,这会才是在做定义。即开辟空间。而后的自动调用的构造函数,是在做初始化。
- const 成员变量,因为常量必须在定义时初始化,之后再也不能改变。
- 引用成员变量
- 自定义类型的成员变量:不使用初始化列表会调用多次构造函数。
注意:
类中变量声明次序就是初始化列表中的初始化顺序。
例题:类成员声明顺序就是构造化顺序。

因为a2声明先于a1,而a2构造列表中需要a1值,但是a1还没有,所以a2是随机值。a1是1。
例题:

静态变量不是常量,静态变量必须在类外初始化,而const常量才是在初始化列表,所以
- 声明:声明变量a,不是定义,不给a分配内存空间。如下两种算声明。一般是带着extern的都叫声明。
还有场景:函数在.h文件中做声明,而在.c文件中做定义和初始化。
extern int a; // 声明
在类中,写出类的成员变量和有哪些函数【不去实现】,也是声明。
- 定义:特指指给分配了空间。如下代码算声明a,也给a定义了。
int a;
- 初始化:给初始值。
特点:
- 必须使用构造函数初始化列表方式做初始化,因为初始化列表处是在做定义的地方,即:在开辟空间了,而构造函数内部:在做初始化,即空间之前已经开辟好了。【要知道创建类在调用构造函数之前,先会默认做初始化列表操作,这会才是在做定义。即开辟空间】
- const成员变量需要在定义时就初始化。所以const成员变量必须在初始化列表时赋值。

这里注意,在后面的const,或const一行没有,不涉及权限缩小。比如上面const Date this就是,且所有this指针,前面的const都是保护this不变,没有权限缩小。如下也是。*
const int x = a;

class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
bool operator>(const Date& d);
// ==运算符重载
bool operator==(const Date& d);
// >=运算符重载
bool operator >= (const Date& d);
// <运算符重载
bool operator < (const Date& d);
// <=运算符重载
bool operator <= (const Date& d);
// !=运算符重载
bool operator != (const Date& d);
// 日期-日期 返回天数
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
Date& Date::operator+=(int day)
{
_day += day;
int tmp = (*this).GetMonthDay(_year, _month);
while (_day > tmp)
{
_day -= tmp;
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
tmp = (*this).GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator+(int day)
{
Date d1(*this);
d1._day += day;
return d1;
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date& Date::operator++(int)
{
Date d = *this;
*this += 1;
return d;
}
重载"-“、”-=":
-、-=
这里调用GetMonthDay时写_year、_month就好,加*this也行,但是难看。
两个日期天数之差
有以下几种思路:
a. 求和公元0年第1天,再作差。
b. 让小日期加到大日期
:: 、sizeof、? . 这四个不要重载。 .*也不能重载。其中点用来做对象访问成员的。
C++必须写返回值类型。
全局和类上都写,会不会重定义:即非要在外面写operator以">"为例,写两个参数,不会编译报错,因为构成重载。一般建议写成员中的。类中的函数,不是属于全局,属于类域。且大部分、习惯上, 都把运算符重载放在类中,所以优先去类中找这也寻找本身更有优势。
最后发现调用成员,优先去成员中找。
使用场景:比如日期类Date,想让Date d1和d3相等。实现opeartor赋值。
不需要有返回值。
Date operator=(const Date & d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}

因为返回了一个Date类型对象,是临时的,它本质通过了拷贝构造。所以调用拷贝构造在返回时。
而使用引用做返回,则一次都不使用。
Date& operator=(const Date & d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}

当重载等号以引用返回,返回的是已经存在的对象,一直用的是等号重载。
此外,极端情况自己给自己赋值,就可以不用处理了,直接判断后跳过。
且用地址比较,不要去比较对象。
this指针是地址,&d也是地址。如果y用:*this!=d,还要对!=做重载,因为两边是两个对象, 不能直接比。
此外,不写赋值运算符重载,编译器会自己生成一个。
此外,不写赋值运算符重载,编译器会自己生成一个。
此外,不写赋值运算符重载,编译器会自己生成一个。
编译器默认生成的赋值重载,根拷贝构造做的事情类似。
如上,如果是队列用两个栈实现,那么这个队列不需要自己实现赋值重载,因为内含Stack拥有,且它自己没有一些连续内存的地址空间。但是如果栈本身,就需要自己实现赋值重载,因为这个自定义类型中使用了指针,需要手动释放。
总结,默认生成的四个默认成员函数,构造和析构处理机制是类似的。
拷贝构造和赋值重载处理机制是基本类似的。
Date d5 = d1;这是拷贝构造,不是赋值重载。
匿名对象
比如:创建一个对象只是为了传参。
一般对象声明周期在当前函数。
匿名对象生命周期只在当前这一行。
如果直接传匿名对象,拷贝构造会少调用一次。
默认成员函数:不写,也可以对这是取到地址,所以说它是默认重载的。
- 默认运算符“=”只是实现了"浅层复制"功能。
- 重载赋值运算符
- 赋值=运算符重载只能在类内重载
下列关于赋值运算符“=”重载的叙述中,正确的是( )
A.赋值运算符只能作为类的成员函数重载
B.默认的赋值运算符实现了“深层复制”功能
C.重载的赋值运算符函数有两个本类对象作为形参
D.如果己经定义了复制拷贝构造函数,就不能重载赋值运算符
根据上面引用,知道A对,BC错,而D:可以有拷贝构造,也可以重载赋值=运算符。
流重载是:双操作数的操作符重载
本质是:对象对 流对象的操作。
所以有两个操作数,必须第一个是操作数,第二个参数是右操作数。
所以不能重载成重载函数。
如果还想cout到左边,不能实现成员函数,所以就做一个全局,不是成员函数,就不区分左右了,因为成员函数,第一个必须是操作数,默认是类类型的,也就是那个对象。
【改进】:写全局运算符重载。不写类运算符重载。