目录
1.理解面向过程和面向对象
2.类的引入
3.类的定义
①成员函数的声明和定义都放在类中。
②成员函数的声明与定义分离
4.访问限定符
5.类的实例化:
空类的大小是多少呢?
6.this 指针
this指针是存储在哪里的?
1.理解面向过程和面向对象
我们常说C语言是面向过程的语言,c++是面向对象的语言。什么是面向过程呢,就是做一件事我们关注做这件事的过程,分析做这个事情的步骤,然后通过函数调用逐步解决问题。
比如洗衣服,我们关注过程的话就是:
拿盆--加水--放衣服--放洗衣液--洗衣服--拧干--晾晒。
如果是面向对象的话,我们解决问题关注的重点就是做这件事的对象,将要完成这件事的对象剥离出来,然后分析对象之间的交互。洗衣服这件事总共四个对象:人,衣服,洗衣机、洗衣液。
整个过程是人和其他三个对象交互完成,但是人不管洗衣机如何清洁衣服,洗衣机也不管人如何将衣服放入洗衣机。
2.类的引入
我们要对一个对象进行描述,方便对这个对象进行管理,也就是我们需要一个结构来存储对象的特征或者属性。C语言中我们使用结构体来描述一个对象:
struct student
{int age;char name;int node;
};
c++中也可以使用结构体来描述一个对象,和C语言不同的是,c++中的结构体可以定义函数:
比如我们对比两种语言实现栈的方式:
C语言版本:数据和方法是分离的
typedef int DataType;
struct Stack
{DataType* _array;size_t _capacity;size_t _size;};
void StacutInit(struct Stack a1,size_t capacity)
{a1._array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == a1._array){perror("malloc申请空间失败");return;}a1._capacity = capacity;a1._size = 0;
}int main()
{struct Stack a1;StacutInit(a1, 3);return 0;
}
c++版本:
typedef int DataType;
struct Stack
{void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容_array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}DataType* _array;size_t _capacity;size_t _size;
};
int main()
{Stack s;s.Init(10);s.Push(1);s.Push(2);s.Push(3);cout << s.Top() << endl;s.Destroy();return 0;
}
可以将函数实现放进结构体的内部,而且直接使用成员变量来进行函数调用。
而上面结构体的定义,c++中更喜欢使用class来代替。
c++完全兼容C语言的玩法。但是也支持升级的技术手段比如类。
3.类的定义
class className
{// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。
类体中内容称为类的成员:
类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者 成员函数。
特点:
①类名就是类型
②类里面可以定义函数
类的定义方式:
①成员函数的声明和定义都放在类中。
成员函数如果在类中定义,编译器会将次函数当做内联函数进行处理。
类的定义的惯例中,成员变相的定义一般为私有
②成员函数的声明与定义分离
也加做类的声明与定义分离。也支持混合,一些定义和声明分离,部分不分离。默认在类里面定义的函数是内联函数。一般情况下定义类采用成员函数的声明与定义分离的方式来定义类。一般长的函数声明与定义分离,短小的函数就定义在类里面。所以,一般将类的声明放在.h文件中,成员函数的定义放在.cpp中,不过要注意的是:
成员函数的定义前要加类名。(主要原因是因为要知道这个函数是哪一个类里面的函数,类定义的也是一个域)如下:
默认在类里面定义的函数是内联函数,不过,具体是不是内联展开函数,由编译器来决定
正常类的定义,长的函数声明与定义分离,短小的函数可以直接放在类里面进行定义。
类的成员变量的命名规则:
(日期类举例)
我们定义一个日期类,并在类内定义一个初始化函数如下:
这里很明显感觉到变量无法区分,但是并没有报错,运行也正常运行,但是我们调式想要观察一下是否真的赋值了:
这里debug版本底下某认是将内联关闭了,方便调试所以可以看见赋值过程,如果发现按f11进入不了调试可以参考文章内联函数进行更改设置就可以进入了。
虽然可以调试,但是我们没有办法分辨这是形参还是成员变量。
这里的几个变量都被认为是形式参数了,我们发现几个变量不论是定义在类里面的私域变量还是函数参数数值都改变了,但是调试可以发现由日期类创建的实例化对象d的成员变量的值没有修改,所以,这里的同名参数都被解释为了形式参数。(原因:局部优先原则,编译器编译的时候会找定义的出处,局部有就使用了)
解决办法①:修改类内函数的参数名字
解决办法②: 方法一修改函数参数名字,使得函数参数的命名不是很规范,代码可读性也就不高了,所以一般情况下c++在成员变量请加一个杠或者后面加杠,表示是一种内部的数据,但是这个不是强制的规定,可能各个公司之间都有一定的习惯差异。
4.访问限定符
花括号包裹的都是一个领域。访问限定符不是领域,它是对类里面成员访问的一个限制。
公有
公有在类外可以访问
保护
私有
二者在继承阶段有区别。
限制类通常这样限制:
影响的是从这个访问限定符到下一个访问限定符,如果后面没有访问限定符,就到结尾
struct不写访问限定符默认是公有的,class默认私有
5.类的实例化:
这个地方是对成员变量进行一个声明,变量的声明和定义的最大区别就在于有没有开空间。而函数的声明和定义很好区分。
成员变量的定义是随着对象的定义而作为对象的成员变量跟着被定义:
Date d;
这是很容易理解的,我们访问成员的时候是要有一个对象才可以访问,而不是直接通过类型就去访问一个成员:
此时的成员变量只是一个声明,告诉我们这个变量的类型和名字,但是赋值时发现这个变量并没有给它开辟空间,无法进行赋值。
类和对象的关系就像设计图和房子,是1V多的关系,用类来实例化对象。
类的大小的计算首先只考虑成员变量的大小再考虑成员内存对齐的规则,成员函数不在对象里面。
原因:我们在调用成员函数的时候,本质是在call一个地址,如果是多个对象调用这个成员函数实际上call的地址是一样的,
但是如果两个对象对自己的成员变量进行访问,访问的是不同的两块空间。
所以,为了节省空间,公用的比如楼下的停车场健身房就没有必要一个房间里面修一个,修一个公共的,但是卧室这个私人空间就一家一个。
内存对齐参照C语言的内存对齐:
- 1. 第一个成员在与结构体偏移量为0的地址处。
- 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
- 3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
空类的大小是多少呢?
空类没有成员的意思是:不需要存储数据,我们好像确实不用为这个空类开空间,但是,当空类实例化了对象的时候,我们如果定义对象呢,如果没有给空类分配空间的话,那么实例化对象的地址难道为空吗?如何表示对象存在过呢
所以,空类也就是无成员变量的类的大小是1个字节,这个字节不存储有效数据,表示定义的对象存在过。
class C
{
public:void func(){};
};
这个类的大小也是1
6.this 指针
在上面我们说,不同的对象可以调用一个函数,下面我们实例化两个对象,初始化自己,然后打印:
现象:两个对象分别调用,各自完成了各自的初始化和打印,上面我们不是说call的函数地址都是一样的嘛,怎么会同一个空间可以保存两个不同的数值嘛,而且还可以是不同的对象访问就是不同的值这是为什么?
原因: C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成。
也就是说每一个在类里面定义的函数的参数中实际上都隐藏了一个参数,我们将这个参数称为this指针:
也就是说原本的代码会被编译器处理成这个样子:
void Print(Date* this)
{cout <<this-> _year << "/" <<this-> _mouth << "/" << this-> _day << endl;
}
调用的地方会做这样的改变:
变成这样:
d1.Init(&d1,2024, 7, 18);
d2.Init(&d2,2023, 4, 17);
d1.Print(&d1);
d2.Print(&d2);
所以不同的对象可以通过指针形参来访问自己私有的数据。 从这一点优化将C语言和c++进行对比比如栈的一些实现方式上可以看出:
首先C语言:
struct Stack s1;
StackInit(&s1);
StackPush(&s1);void StackInit(struct Stack* ps);
c++:
Stack s1;
s1.Inot();
s1.Push();void Init()
{
a = 0;
:
:};
c++中编译器帮程序员做了很多的事。
但是不能显示写this的实参和形参。但是可以在类里面显示的使用
实际定义中this指针类型为 类的类型名 * const this,也就是意味中this指针本身指向的对象不能够被改变,指向对象的内容可以被修改。
this指针是存储在哪里的?
this指针作为形式参数,是存放在栈帧上的。vs放入了寄存器中。如果是是属于对象,计算对象大小就应该纳入计算。
我们可以通过转到反汇编查看:
空指针是运行错误,编译错误是检查语法问题。
区分以下两段代码:
class A
{public:void Print(){cout << "Print()" << endl;}
//private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
这段代码的结果是正常运行:
修改如下:
原因:
this虽然是个空指针,但是我们并没有访问空指针所以就会报错。当我们要使用这个指针来访问成员变量的时候就要访问this指针,此时就会发生报错。