目录
5.类的作用域
6.类的实例化
6.1成员的声明和定义
6.2实例化出的对象大小
7.类对象模型❗❗
7.1如何计算类对象的大小
7.2类对象的存储方式猜测
7.3结构体内存对齐规则
7.3.1内存对齐
7.3.2大小端
8.this指针
8.1this指针的引出
8.2this指针的特性
C和C++实现栈的对比
- 类,对象,成员变量,变量,成员函数的关系
5.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。
- 类域能解决一定的命名冲突问题
- 在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
//声明.h
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
//定义.cpp
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}
6.类的实例化
用类类型创建对象的过程,称为类的实例化。
- 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员。
- 定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
- 类是没有空间的,只有类的实例化出的对象才有具体的空间。
举例:
- 类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。
- 谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊
举例:做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
6.1成员的声明和定义
- 无论是成员变量/成员函数在类中,只要还未被实例化成对象,那么它们都是一种声明,因为它们只是类,并不存在空间。
- 定义并不是给值,而是为类开辟空间,也就是实例化。
- 定义的时候可以初始化,但是只初始化并不代表定义。
#include<stdlib.h>
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
//这里是声明还是定义?声明
int _year;
int _month;
int _day;
};
int main()
{
Date s;//定义了
s.Init(1,1,1);
❌ Date::_year++;//不能因为它只是声明,并没有开辟空间
❌ Date._month = 100; // 编译失败:error C2059: 语法错误:“.”
return 0;
}
6.2实例化出的对象大小
移步☞类对象的存储方式。
7.类对象模型❗❗
《深度探索C++对象模型》这本书中对对象模型的描述如下:
有两个概念可以解释C++对象模型:
- 语言中直接支持面向对象程序设计的部分。
- 对于各种支持的底层实现机制。
对象模型是指在面向对象编程中,为了描述和处理现实世界中的实体、事物或概念所构建的抽象模型。它通常由类、对象、属性、方法等元素组成,用于描述实体之间的关系、结构和行为。
❓❓问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?类对象的存储方式?如何计算一个类的大小? 下面我们来一起探讨。
7.1如何计算类对象的大小
- 类的实例化=对象
- sizeof(类名)
- sizeof(对象)
- 二者计算都是类实例化的大小,也就是类对象的大小。
- 一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐。
- 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
- 没有成员变量的类对象大小1字节,标识对象实例化,定义出来存在。
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
char _i;
};
❓计算下面类对象的大小: sizeof(A1) : ______ sizeof(A2) : ______ sizeof(A3) : ______
#include<stdlib.h>
#include<iostream>
using namespace std;
// 类中既有成员变量,又有成员函数
//内存对齐存储
class A1 {
public:
void f1() {}
private:
int _a;
char _i;
};
// 类中仅有成员函数
//标识对象实例化,定义出来存在
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
int main()
{
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
7.2类对象的存储方式猜测
类对象的存储方式有两种:
- 对象中包含类的各个成员(不用)
- 代码只保存一份,在对象中保存存放代码的地址(以后)
- 只保存成员变量,成员函数存放在公共的代码段(现阶段)
//现阶段
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
//不是一块空间,成员变量各自是各自的空间
d1._year;
d2._month;
//是一块空间,成员函数是一块空间
d1.Init(1,1,1);
d2.Init(1,1,1);
return 0;
}
【对象中包含类的各个成员】
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码(成员函数的地址),相同代码保存多次,浪费空间。那么如何解决呢?
【代码只保存一份,在对象中保存存放代码的地址】
【只保存成员变量,成员函数存放在公共的代码段】
7.3结构体内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
- VS中默认的对齐数为8。
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
【面试题】
- 1. 结构体怎么对齐? 为什么要进行内存对齐?
- 2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
- 3. 什么是大小端?如何测试某机器大端还是小端,有没有遇到过要考虑大小端的场景。
- 前面C语言进阶我们也讲解过(结构体)C语言之自定义类型_结构体篇(1)-CSDN博客
7.3.1内存对齐
- 怎样内存对齐?
- 类对象的包含了什么,需要包括成员函数吗?
- 为什么要内存对齐?
- 可以改变对齐参数吗?
【怎么内存对齐---按照规则】
类的对象存储空间只包含了成员变量,成员函数在公共区域。
#include<iostream>
using namespace std;
struct A
{
private:
int _a;
char _i;
};
struct B
{
private:
char _i;
int _a;
};
【为什么要内存对齐】
- 对齐是为了提高效率!
- 硬件规定CPU一次只能读取4/8个字节。
- 32/64个线,32/64个位,组成0/1的二进制数。(消耗相同,一次只读取32/64位,4/8个字节)
- 硬件规定:CPU的控制器只能从整数倍开始读取。起始位置必须是整数倍的位置。
- 以上规定从而导致了:对齐和不对齐读取存在效率上的问题。
- 对齐读取的次数比不对齐读取的次数多。
【对齐可以改变编译器默认对齐数的大小】
#include<iostream>
using namespace std;
//改变默认对齐数的大小
#pragma pack(1)//相当于不对齐
struct A
{
private:
int _a;
char _i;
};
struct B
{
private:
char _i;
int _a;
};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
return 0;
}
7.3.2大小端
8.this指针
8.1this指针的引出
8.2this指针的特性
C和C++实现栈的对比
【C语言】
可以看到,在用C语言实现时,Stack相关操作函数有以下共性:
- 每个函数的第一个参数都是Stack*
- 函数中必须要对第一个参数检测,因为该参数可能会为NULL
- 函数中都是通过Stack*参数操作栈的
- 调用时必须传递Stack结构体变量的地址
结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据方式是分离开,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。
【C++】感谢祖师爷
- C++中通过类可以将数据 以及 操作数据的方法进行完美结合。
- 通过访问权限可以控制那些方法在类外可以被调用,即封装。
- 在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。
- 而且每个方法不需要传递Stack*的参数了。
- 编译器编译之后该参数会自动还原,即C++中 Stack *参数是编译器维护的,C语言中需用用户自己维护。
【C语言实现】
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct Stack
{
DataType* array;
int capacity;
int size;
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == ps->array)
{
assert(0);
return;
}
ps->capacity = 3;
ps->size = 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
if (ps->array)
{
free(ps->array);
ps->array = NULL;
ps->capacity = 0;
ps->size = 0;
}
}
void CheckCapacity(Stack* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity * 2;
DataType* temp = (DataType*)realloc(ps->array,
newcapacity * sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
ps->array = temp;
ps->capacity = newcapacity;
}
}
void StackPush(Stack* ps, DataType data)
{
assert(ps);
CheckCapacity(ps);
ps->array[ps->size] = data;
ps->size++;
}
int StackEmpty(Stack* ps)
{
assert(ps);
return 0 == ps->size;
}
void StackPop(Stack* ps)
{
if (StackEmpty(ps))
return;
ps->size--;
}
DataType StackTop(Stack* ps)
{
assert(!StackEmpty(ps));
return ps->array[ps->size - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->size;
}
int main()
{
Stack s;
StackInit(&s);
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackPop(&s);
StackPop(&s);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackDestroy(&s);
return 0;
}
【C++实现】
#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
void Init()
{
_array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = 3;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
DataType Top() { return _array[_size - 1]; }
int Empty() { return 0 == _size; }
int Size() { return _size; }
void Destroy()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_array, newcapacity *
sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Destroy();
return 0;
}
🙂感谢大家的阅读,若有错误和不足,欢迎指正。