【C++】类和对象
- 前言
- 遗漏的部分
- 内联函数
- 使用注意
- 语法糖
- auto
- 循环(:)
- 正篇:面向对象(上)
- 面向对象的思路
- 类和对象
- stuct的升级
- 对象
- class
- 封装(private protect public)
- 定义和声明分离
- this指针
前言
这个篇章算是博主学习的一个里程碑
终于踏上了面向对象的语言的编程模式的正式学习
这里博主会写三篇博客来讲解类和对象。
遗漏的部分
这里是没有讲完的C++的优化的小部分,因为篇幅很小,所以选择在这里补上
内联函数
众所周知,程序的运行就是不停的调用和执行函数
从main函数开始,到return 0 视为程序的运行的结束
按照之前讲的函数栈帧的知识,当调用函数时,就会在栈上申请空间,创建变量,执行代码,执行结束后,就销毁栈帧。
但是当我们遇到一个需要被频繁调用的函数时,就会花费大量时间在栈帧的创建和销毁上,十分影响效率。
在C语言中,如果被频繁调用的代码我们会使用宏定义去进行优化
原理就是define的代码会将代码直接进行展开,避免了栈的申请和销毁空间的时间花费。
而在C++中,祖师爷特意进行了一个优化功能
那就是inline
相比于宏定义,内联函数更好控制,并且可以进行调试
inline int Add(int x, int y)
{
return x + y;
}
加了inline后,就会进行类似于C的宏展开效果
但是inline并不能直接将函数直接变为内联函数,只是向编译器的一个建议,最后还是要编译器决定
这里就要谈谈内联函数的局限了
当函数代码量过于大时,对函数代码进行进行展开后
从这里可以看出内联函数的局限
只有代码体量小并且调用频繁的代码才适合inline展开。
使用注意
1.inline虽然相比于宏定义能调试,但是只能在debug下进行调试,在debug下还是以普通代码进行运行(这样可以进行调试),当release下,才进行代码展开,以内联函数进行运行,但release下也就不能进行调试了。
2.inline关键字只是对编译器的建议,真正是否将函数进行代码展开还是取决于编译器,防止用户瞎用
- 内联函数不能定义和声明分离,最好全写在头文件中
因为内联函数使用时不需要像其他函数一样生成call去调用函数,而是直接将代码展开
而声明和定义分离是通过地址去调用实现的,内联函数没有地址(因为展开了)所以不能调用,所以不能定义和声明分离
语法糖
auto
auto算是C++更新的一个语法糖。
auto会根据右边的表达式决定左边的类型
例:
Int x=10;
auto a=&x; //——auto会自动识别类型
但是auto* b=x
是错误的
因为auto* b固定为指针类型,接下来就是判断是什么类型的指针。
并且引用也是可以的
int x=10;
auto & c =x;
这样是创建了一个指向x的引用。
现在可能还看不出来这个auto有多方便,但是以后会很方便,但是博主现在也是才疏学浅,所以这里只能小小提一下
循环(:)
int arr[3] = { 1,2,3 };
for (int c : arr)
这里的:,代表的意思是:
将arr的每个元素,都遍历赋值给c
所以这里我们可以打印C,来做到打印数组的值
int arr[3] = { 1,2,3 };
for (int& c : arr)
{
cout << c << endl;
}
那如果我们想改变数组中的值呢?
这里我们只要给c加一个引用就可以了。
int arr[3] = { 1,2,3 };
for (int& c : arr)
{
c++;
}
for (int i = 0; i < 3; i++)
cout << arr[i] << endl;
for (int& c : arr)
这里就代表将arr的数据都赋值给引用变量C
而C为arr元素的引用
所以改变C就会改变arr的元素。
正篇:面向对象(上)
面向对象的思路
大家都知道以前学过的C是面向过程的语言。
而像现在耳熟能详的C++和java等都称作面向对象的语言
面向对象和面向过程是两种不同的开发思路,可以认为面向对象是比面向过程更高级的开发方式
就比如要送一顿外卖
在面向过程看来:
就要分解成
接单 买食材 做菜 包装 取餐 送菜 结束订单
这里的送外卖是我们要达成的目标
而送外卖的每一个过程都被细分出来,都进行分别的独立实现
那现在来看看面向对象:
顾名思义就是将送外卖的方法通过细分成类
商家 外卖平台 外卖员 顾客
这里送外卖被细分成了四类
在面向过程中被细分的送外卖的过程部分,都分别分配到每个类中去实现
这里看起来可能没有啥高级的,但是结合生活来说
当不同的过程有专门的人或机构来执行时,效率和成本都能大大减少
在程序开发中也是有很多好处的,这需要在不停的学习中去感受,这里凭空而谈的话也是十分抽象,不易理解。
类和对象
说到面向对象,那肯定是要先学习类和对象了
前面我们知道,类是具有一定功能集合体
而要实现功能,就需要两个要素:
1.变量
2.函数
这两个可以说是所有程序都需要的最基本的两个要素了
所以说我们的类,需要具有函数和变量
所以类包括
1.成员变量
2.成员函数
所以类可以看作是成员变量和成员函数的集合体
看到这里有没有大家有没有想到C语言中的老朋友
stuct的升级
struct
它在C语言中是结构体(自定义变量)
可以包括成员变量,但是没有成员函数
这个时候祖师爷就把他升级为了类
让他可以包括成员函数了
struct A
{
int add()
{
return _x + _y;
}
int _x;
int _y;
};
这里就是我们创建了一个A类
A类里面包括了
成员函数:add
成员变量: _x _y
对象
那我们知道类了以后,那什么是对象呢?
这里可以生动的表示理解为:图纸和房子的关系
比如说,类为图纸,以类为创建的变量就是对象。
例:
这里创建了一个z类
在main函数中,用Z a
,就是说以Z类为图纸,创建了一个Z类的变量a
这个时候a就是对象。
这里我们调用类中的a的对象的函数的时候用的是像结构体以前用的(.)操作符
**为什么不用类的::操作符?**这后面会有解释。
同时类也是单独的一个域,里面的成员函数和成员变量在访问时都需要单独的访问符号
同时也有一个问题出来了,以前计算struct的大小,只需要计算变量即可
但是现在成为类后有了成员函数,那struct的大小该怎么计算
直接告诉结果吧。
类的计算和原本的struct计算方式一样,只需要注意内存对齐+计算成员变量即可,因为成员函数存储在公共的代码区。
那如果创建了一个空类呢?
这里看到空类和只有成员函数的类字节都为1
这里留一个字节是系统知道用户创建了一个类,留了个空间
同时也证明了,成员函数不占空间,而是在公共的代码区。
这里放上以前的struct计算方法(也是以前写的博客)以前struct大小计算
将struct升级成了类后,可能是祖师爷觉得不够用,选择创建了一个单独的类
class
class z//创建了一个类型z
{
int add(int x, int y)
{
_x = x;
_y = y;
return _x + _y;
}
int _x;
int _y;
};
这样创建了一个类型z后,使用时会发现有问题
创建了一个类后发现却无法调用成员变量和成员函数
这里报错时显示的时显示的是无法访问
这里就要扯到面向对象三大特点的之一的封装了
封装(private protect public)
学过C的人都知道,当我们在使用和写函数时,过于自由
就比如当我们写了一个栈时,其中的capacity和date等成员变量
就会被人直接拿来修改和调用
Stack.capacity=0
,当capacity被改变后,Stack会乱套的所
以为了防止以上缺德的行为,就在类中设置了权限
总共有三种权限情况:
private protect public
public:允许在类域外进行调用
private和protect(主要区别在继承):不允许类域外的进行调用,但是类域内的成员函数还是能随便调用
从我们上面的错误可以知道
在class中我们没有设置权限的时候,默认为private状态,不允许类域外进行调用。
在struct却可以进行调用,因为struct默认成员变量和函数都是public(因为要兼容C,C中的struct为默认公开状态)
那怎么样可以改变class中的权限呢?
这里就可以发现调用函数成功了。
但是这和我们C语言中的高度自由有什么区别呢?
又可以对类中的成员变量进行随意改变了
所以一般来讲,将接口(函数)进行public
将成员变量进行private,来防止别的用户对成员变量进行随便改变
这里就可以发现不能随意在类域外对成员变量进行改变了。
定义和声明分离
这个也是C中的经典玩法了
但是在C++中,类成为了单独的一个域
众所周知,不同的域中的变量和函数可以重名,所以不能像以前一样直接对函数进行定义。
这里拿我们以后会将到的运算符重载演示一下:
bool operator<(const Date& d);//声明
//加了类域限定符
bool Date::operator<(const Date& d)
{
if (_year < d._year)
return true;
else if (_year == d._year && _month < d._month)
return true;
else if (_year == d._year && _month == d._month && _day < d._day)
return true;
return false;
}
这里我们就能清楚地看到,进行函数的定义的时候需要加上特定的类域限定符。
this指针
还记得留了两个坑吗
1:为什么调用类的函数时,是用对象去调用,而不是用域限制符去调用?
2:
我们知道类的函数在公共区域,就是说对象a1和a2调用的函数一样
并且没有传参数,为什么print的结果却是不一样的?
这里就要牵扯到this指针了。
实际上它的调用是这样的。
print(a1)
print(a2)
他是进行传参的,只不过这个时编译器替我们做了而已,相当于隐形传参
并且在函数中是可以进行调用的
void print()
{
this->_x += 10;//this就是代表调用的对象的指针
cout << _x << endl;
}
但同时,编译器不允许我们自己去进行传参
所以现在的两个问题都迎刃而解了。
1.为什么不能用域限定操作符来进行访问
z::print();
因为这样就和对象没有关系了,无法传输对象的指针。
2.为什么a1和a2没有传参,调用的函数一样,打印结果却不同
看起来没有传参,其实在调用对象的函数的时候,就已经进行隐藏传入了对象的指针,所以才能区分a1和a2的_x
那这样还有一个问题了?
那这个this指针是存储在哪的?
毫无疑问,是存放在栈区中的,当函数调用结束的时候就会进行销毁。