目录:
1.类的引入
2.类的定义
3.类的访问限定符以及封装
4.类的作用域
5.类的实例化
6.类的对象大小的计算
7.类成员函数的this指针

1.类的引入

在c语言中结构体里面只能定义变量,但是在c++中结构体中不仅可以定义变量,也可以定义函数,举一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Stack.h文件
#pragma once
#include<iostream>
#include<cstdlib>
#include<cassert>
using namespace std;
typedef int DataType;
struct Stack//这里写的是一个栈,栈的特点就是后进先出
{
void Init(size_t DataType);//size_t是一种无符号整数
void Push(DataType x);
DataType Pop();
void Destroy();
private:
DataType* _a;
int _capacity;//指的是内存开辟了多少空间
int _size;//指的是实际有效的数字的个数
};



Stack.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void Stack::Init(size_t capacity)//size_t是一种无符号整数
{
_capacity = capacity;
_a = (DataType*)malloc(sizeof(DataType) * _capacity);
if (_a == nullptr)
{
perror("malloc");
return;
}
_size = 0;
}
void Stack::Push(DataType x)
{
assert(_a);
if (_size == _capacity)
{
_a = (DataType*)realloc(_a,sizeof(DataType) * _capacity * 2);
if (_a == nullptr)
{
perror("malloc");
return;
}
_capacity *= 2;
}
_a[_size] = x;
_size++;
}
DataType Stack::Pop()//这里的每个函数都是在类作用域里面,忘记检查边界问题了,只想到了内存大小的问题
{
assert(_a);
assert(_size);
_size--;
return _a[_size];
}
void Stack::Destroy()
{
free(_a);
_a = nullptr;
_size = 0;
_capacity = 0;
}

2.类的定义

1
class className{//类体:由成员函数或者成员变量组成};

class是类的关键字,className是类的名字,{}是类的主体,注意,类定义结束时后面的分号不能省略。

值得一提的是类的两种定义方式:

1.声明和定义全都放在类体中,需要注意:成员如果在类中定义,编译器可能把其当成内联函数处理,也就是在调用这个函数的时候,有可能在调用处被展开,这样就不需要为了调用函数建立栈帧

2.声明在.h中(其实就是在类中啦),成员函数的定义在.cpp中。当你创建一个类的时候,里面的东西就相当于被封在了类域里面,你如果要使用的话,需要类名+::

注意!!!: 一般情况下我们都采用第二种方式

成员变量命名规则:一般都在成员变量的名字前加上一个_或者m


3.类的访问限定符以及封装

c++实现封装的方式:用类将对象的属性(成员变量)和方法(成员函数)结合在一起,让对象更加完善,通过访问权限 选择性的将接口提供给外部的用户。访问限定符有:1.公有(public)2.私有(protected和private)

访问限定符的说明:

1.public修饰的成员在类外可以直接被访问(使用类名+::+你要访问的成员,这是因为类本身就是一个新的域)

2.protected和private修饰的成员在类外面不能直接被访问

3.访问权限的作用域从该访问限定符出现的位置直到下一个访问限定符出现位置

4.如果后面没有访问限定符,作用域就到类结束

5.class的默认访问权限是private,Struct的默认访问权限是public


4.类的作用域

类定义了一个新的作用域,在类外定义函数或者变量的时候(我们一般都在类中进行声明,而不进行定义),需要使用::(作用域操作符)知名成员属于哪个作用域。这也就解答了为什么我们在包含头文件对成员函数进行定义的时候要使用作用域操作符,因为编译的预处理阶段也只是把头文件展开了,如果要访问成员函数或者变量照样是要使用作用域操作符


5.类的实例化

用类的类型创建变量的时候就成为类的实例化

1.类是对对象进行描述的,并没有分配实际的内存空间来存储他,就是相当于一个图纸,只是按照他来建造房子,但是并没有房子被建造,是不耗费空间的

2.一个类可以实例化出多个对象,实例化出的对象,占据实际的物理空间,存储类成员变量


6.类的对象大小的计算

我们首先要知道几点结论:1.类和类所实例化出来的对象的大小是一样的    2.对象中的存储方式:只保存成员变量,成员函数放在公共代码段(所以成员函数并不存在对象中)    3.一个类的大小就是该类中“成员变量”之和,当然还要注意内存对齐(下面我们会再复习一下)  4.一个空类的大小是一个字节(如果你不给它安排大小,那么他定义出来的变量就是不占内存,也就是无地址,所以编译器给它一个字节就是为了占位)

内存对齐规则:

1.对齐数是该成员大小和编译器默认对齐数的较小值

2.结构体的总大小为最大对齐数(最小对齐数中的最大对齐数)的最小倍数

3,如果嵌套了一个函数体,那么那个函数体就对齐到自己最大对齐数的整数倍处,结构体的整体大小就是最大对齐数的最小倍数


7.类成员函数的this指针

7.1this指针的引出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year,int mounth,int date)
{
_year = year;
_mounth = mounth;
_date = date;
}
void Print()
{
cout << _year << "-" << _mounth << "-" << _date << endl;//在这里不能
}
private:
int _year;
int _mounth;
int _date;
};

int main()
{
Date d1;
d1.Init(2025, 5, 15);
Date d2;
d2.Init(2005, 11, 13);
d1.Print();
d2.Print();
return 0;
}

有一个问题:我们知道成员函数保存在代码段里,他没有关于不同对象的区分,但是我们在这里调用d1和d2的时候明显发现了区分,这是为什么呢?

c++中通过引用this指针来解决这个问题,c++给每个“非静态成员函数”(所谓的非静态成员函数就是实例化的对象,通过对象访问的成员函数就是非静态成员函数(注意:成员函数不存储在对象里,所以this指针也不在对象中)。 相反,没有经过实例化的成员函数也就是类中的函数叫作静态成员函数,里面是没有隐藏指针的)增加了一个隐藏的this指针参数,让这个参数去指向对象,在函数体重所有“成员变量”的操作都是通过该指针去访问。

7.2this指针的特性

1.this指针的类型const修饰的类类型*(指的是指针所指向的那块空间里的内容不能改变),并且不能在形参/实参处显示传递,但是可以显示使用(在内部也不能改变this指针指向的空间的内容,因为被const修饰了)。

2.只能在“成员函数”的内部使用

3.this指针本质上是成员函数的形参,当对象被调用时,将对象的地址作为实参传递给this指针,所以对象中不存储this指针

4.this指针是“成员函数”的第一个隐含的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递

两个小小的测试题:

1.这个代码会崩掉吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year,int mounth,int date)
{
_year = year;
_mounth = mounth;
_date = date;
}
void Print()
{
cout << "print" << endl;
//cout << _year << "-" << _mounth << "-" << _date << endl;//在这里不能
}
private:
int _year;
int _mounth;
int _date;
};

int main()
{
Date* d1 = nullptr;//定义了一个类的指针,用来存放对象的地址
d1->Print();
return 0;
}

答案是不会的。1.print不存在于对象中,而是存在于公共代码段,所以不存在非法访问。2.因为传入的是一个对象的地址,那么this指针就是一个nullptr,是一个空指针,但是在函数李米娜这个指针并没有被解引用,所以这一处也不存在非法访问。

2.这个代码会不会报错呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year,int mounth,int date)
{
_year = year;
_mounth = mounth;
_date = date;
}
void Print()
{
cout << _year << endl;
//cout << _year << "-" << _mounth << "-" << _date << endl;//在这里不能
}
private:
int _year;
int _mounth;
int _date;
};

int main()
{
Date* d1 = nullptr;//定义了一个类的指针,用来存放对象的地址
d1->Print();
return 0;
}

答案是会的,因为你在print中访问了this指针,this指针是空指针,所以存在非法访问

注意: 1.可能有的同学会说,为什么不直接通过类调用函数(也就是静态成员函数),大家要注意,如果你用类调用成员函数的话就无法传入this指针    2.有的同学还会说,调用的时候传入一个this指针不就行了吗?但是大家请注意,this指针不能被显示传递