"努力准没错"
都说C++语法难学,其实当找到正确的打开方式(学习任何知识都是这样),学习起来都会变得游刃有余。
-----------前言
何为面向对象,在我看来,就是从一个聚焦事物的过程,移动到抽象这个事物的本质属性。
一、隐藏的this指针
(1)何为隐藏this指针?
我们先来看看如下的代码片段。
显然有 两个问题伴随着这份代码而出现:
①Date类中,d1,d2同样去调用类成员函数,其类成员函数是怎么知道谁去调用了他?(我们并没有给类成员函数传特定参数!!!)。
②类成员函数并不是存储在类里,在类开空间。而是都会放在一块公共的代码区。
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
可以这样理解;
因此,这样看似没有传,实质上肯定把函数自己传给类成员函数的叫做 “this”指针。
那这样就讲完了吗?肯定不是!
(2)this指针的特性
①const类型,即不能给this指针赋值 (而不是this指针指向的对象)!
②this指针本质是个形参。
③只能在“成员函数”的内部使用。
④this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递。。
const类型:
下面两个问题;
this指针被保存在哪里?this指针可以为空吗?
以下程序的运行结果? 编译报错?运行出错?正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
从根本上! this指针就是存在栈上的!因为需要进行形参传递。
this指针根本不能为nullptr,任何类部成员函数为什么能够访问到 成员私有变量;不同的类调用同样一份存在 公共代码区的函数,为什么能够找到、初始化自己的那一份类部成员? 根本上是因为this指针的存在!
this指针 与 函数调用
我们来看看 如下场景;
二、类的默认成员函数
(1)如何理解空类?如何理解空类的大小?
①如果一个类中什么成员都没有,简称为空类;
②任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
(2)构造函数
①构造函数的特性
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 支持函数重载。5.在对象整个生命周期内只调用一次。
6.用户显示定义构造函数后,C++编译器也就不再为之生成(无参)默认构造函数。
②如何理解编译器默认生成构造函数
内置类型不做处理、但对于自定义类型,回去调用它的默认构造函数。
什么是默认构造函数? 无参!
1.编译器默认生成的 2.全缺省 3.不带参
③C++11成员函数的默认值
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁
注:
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
因此,无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数。都可以认为是默认的构造函数
(3)析构函数
在使用C语言,一旦使用到数据结构的栈、队列、链表.....难免有时候,会 忘记对在堆上申请的空间进行手动释放。鉴于C语言对动态内存管理的使用缺陷,也就有了析构函数。
与构造函数功能相反,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
①析构函数的特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。4.析构不支持重载。
5.对象生命周期结束,C++编译系统会自动调用析构函数。
②析构销毁的次序
③析构的其他场景
析构函数同样有 与构造函数一样的处理方式。对于 内置类型 调用自己的析构,对于自定义类型,会去调用它的析构函数。
(4)拷贝构造
有那么一种情况, 能否用已经构造好的一个类 ,通过一种方式,在用已存在的类类型对象创建新对象时由编译器自动调用。
①拷贝构造的特性
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝(值拷贝)。
②如何理解Date 必须传参数引用?
③如何理解深、浅拷贝
如果针对于内置类型,完成只完成值拷贝,那自然不会出任何问题,但是对于 那些动态申请管理的场景呢?
从而导致,st1 st2都会在自己生命周期结束时调用 析构,反复对free(_a);
因此面对这种情况,就需要我们自己写拷贝构造 而不是一味丢给编译器去生成。
(5)赋值运算符重载
对于内置类型,系统可以识别对其进行+、-、*、& ....... 那如果是自定义类型,想使用这些呢?C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
但:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个"类"类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义4..* :: sizeof ?: . 不能进行重载
①赋值运算符重载的返回值
1.const T&,传递引用可以提高传参效率
2.返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值4.返回*this :要复合连续赋值的含义
同样,用户不显示定义,编译器默认会生成 赋值运算符重载 , 对内置类型进行值拷贝,对自定义类型进行浅拷贝。
②operator的前后置 ++、--
③operator 与 cin、cout
那么我们如何 对自定义类型 进行 ">>" "<<"的 运算符 重载呢?
cin的重载亦然,也就不再多赘述。
三、初始化列表
(1)为什么要有初始化列表?
我们知道,类的定义在如图所示处。这是类整体的定义。
那么类的成员变量 是定义在上面时候呢?
class Ref
{
Ref()
:a(1),
b(2.2)
{}
private:
int a;
double b;
}
注意格式.
当然,不仅仅是const 还包括引用(types&) .
因此,类的成员变量的初始化 不是在 类的构造函数。
而是在 初始化列表里!
(2)初始化列表 与 构造函数
类的每个成员都要走初始化列表,就算不显示,也会走。
1.初始化列表对于 内置类型 有缺省值(C++11专门打的补丁) 则用,否则就随机值
2.初始化列表对于 自定义类型 会去调用它的 默认构造函数,如果是显式调用 则会去调用显式的构造函数。
初始化 列表的初始化顺序 受 声明的先后决定。
总结:
1.能使用初始化了列表 尽量使用。
2.一个类尽量 都提供默认构造函数。
四、explicit 关键字
(1)explicit是什么?
我们先来看看如下场景;
因此,
单参构造函数,没有使用explicit修饰,具有类型转换作用。
explicit 修饰构造函数,禁止类型转换 ---explicit 去掉之后,代码可以通过编译
五、static成员变量 和 成员函数
(1)生命周期 与 作用域
生命周期:针对的是 变量 存在时间长短。
作用域:针对的是 变量 使用、访问的 区域。
(2)static 的 不同作用域
①全局static
②局部static
③类里面的static
(3)类里面的 static成员变量 与 成员函数
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
五、匿名对象
匿名对象 的生命周期 只在当前命令行。
(1)编译器对 构造、拷贝构造的优化
①匿名对象传参
②匿名对象 做返回值
因此在实际应用中,尽量减少 拷贝构造。能用匿名对象 就用匿名对
象。