文章目录
- 前言
- 一、类
- 1.1 类的定义
- 1.2 类的书写和使用
- 1.3 访问限定符
- 1.4 类域
- 二、实例化
- 2.1 实例化概念
- 2.2 对象大小
- 三.this指针
- 总结
前言
前面的几篇文章我们介绍了命名空间,inline,nullptr等C++
中常见的的基础概念。今天的文章我们来介绍一些C++中类与对象的内容。
一、类
1.1 类的定义
在现实世界中,经常有属于同一类的对象。例如,你的自行车只是世界上很多自行车中的一辆。在面向对象软件中,也有很多共享相同特征的不同的对象:矩形、雇用记录、视频剪辑等。可以利用这些对象的相同特征为它们建立一个集合。而这个集合就称为类。类是定义同一类所有对象的变量和方法的蓝图或原型。
1.2 类的书写和使用
class Stack
{
public:
void Init(int n=4)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc");
return;
}
a = tmp;
size = top = 0;
}
void Push(int x)
{
if (top == size)
{
int newsize = size * 2;
int* tmp = (int*)realloc(a, sizeof(int) * newsize);
if (tmp == NULL)
{
perror("realloc");
return;
}
a = tmp;
}
a[top] = x;
top++;
}
private:
int* a;
int top;
int size;
};
class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year; // year_ m_year
int _month;
int _day;
};
在定义类的成员名称时,为了与函数的形参区分,一般会在变量名前面加上"_"或者m,这个在C++标准中并没有明确规定,根据实际需求使用。
struct ListNodeCPP
{
void Init(int x)
{
next = nullptr;
val = x;
}
ListNodeCPP* next;
int val;
};
C++中的结构体也可以定义类,C++中兼容C的结构体并将其升级为类。在结构体内部也可以定义函数。并且在C++中结构体的名字直接可以作为类型名,这就省去了我们通过typedef简化掉struct的过程。但是我们还是推荐使用class来定义类。
在类中定义的函数的类型默认为inline。
1.3 访问限定符
在上面的Stack类中,我们发现有public和private这两个单词。这就是访问限定符。访问限定符有三个
• 这是C++⼀种实现封装的方式,用类将对象的属性与方法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
• public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,在以后继承的内容中才能体现出他们的区别。
• 访问权限作用域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为止,如果后面没有访问限定符,作用域就到}即类结束。
• class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
• ⼀般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。
•在一个类中可以有多个public,private等限制符,但是方便起见,我们一般把他们放在一起。
1.4 类域
- 类定义了⼀个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。
- 类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到数组a等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的a等成员,就会到类域中去查找。
class Stack
{
public:
void Init(int n = 4);
private:
int* a;
size_t capacity;
size_t top;
};
// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
a = (int*)malloc(sizeof(int) * n);
if (nullptr == a)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
二、实例化
2.1 实例化概念
⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。我们创建出一个类,相当于画出了一个图纸,我们通过这个图纸建造出的房屋就是实例。所以实例化就是从图纸到房屋的过程。我们只定义出一个类,它是不用空间,只有将它实例化后才会开辟空间。
2.2 对象大小
首先我们先来分析一下类对象中有哪些成员?成员函数+成员变量。类实例化后的每个对象都有独立的数据空间,所以成员变量是要占据一定空间的。那么成员函数是否占用空间呢?首先函数被编译后是一段指令,对象中无法存储,这些指令存储在一个单独的区域(代码段),如果对象中想要存储他,就只能存储成员函数的指针。我们在分析一下,每一个实例的成员变量都有自己的空间是因为,每一个实例的变量可能不同所以单独存储,但是每一个实例中的成员函数的指针是一样的,如果每实例化一个对象都开辟空间就会产生浪费。所以成员函数是不储存在对象内的。其实函数指针不需要存储,函数指针是一个地址,调用函数被编译成汇编指
令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址。
上面我们分析了对象中只存储成员变量,C++规定类实例化的对象也要符合内存对齐的规则。
内存对齐规则
• 第⼀个成员在与结构体偏移量为0的地址处。
• 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
• 注意:对齐数=编译器默认的⼀个对齐数与该成员大小的较小值。
• VS中默认的对齐数为8
• 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
• 如果嵌套了结构体的情况,嵌套的结构体对齐到在自己的最大对齐数的整数倍处,结构体的整体大小
就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
我们再来看一段代码,思考它的结果是什么
class A
{
public:
void Print()
{
//...
}
};
class B
{};
int main()
{
A b;
B c;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
return 0;
}
上面的程序运行后,我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个字节呢?因为如果⼀个字节都不给,怎么表示对象存在过呢?所以这里给1字节,纯粹是为了占位标识对象存在。
三.this指针
this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。
例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .
class Date
{
public:
// void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day)
{
// this->_year = year;
_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。
关于this指针的一个经典回答:
当你进入一个房子后,
你可以看见桌子、椅子、地板等,
但是房子你是看不到全貌了。
对于一个类的实例来说,
你可以看到它的成员函数、成员变量,
但是实例本身呢?
this是一个指针,它时时刻刻指向你这个实例本身
总结
以上就是今天的全部内容,主要介绍了一些C++的基础内容,C++的内容还有许多,如果喜欢这篇博客,期待你的一键三连。