C++类与对象深度解析(一):从抽象到实践的全面入门指南
文章目录
- C++ 类与对象——详细入门指南
- 前言
- 1. 类的定义
- 1.1 类定义的基本格式
- 示例代码
- 解释
- 1.2 访问限定符
- 示例代码
- 解释
- 1.3 类域
- 示例代码
- 解释
- 1.4 成员命名规范
- 常见的命名约定:
- 示例:
- 拓展:
- 1.5 class与struct的默认访问权限
- 示例:
- 2. 类的实例化
- 2.1 实例化概念
- 示例代码
- 解释
- 2.2 对象的大小与存储
- 示例代码
- 解释
- 2.3 对象大小:空类的情况
- 空类对象的大小
- 为什么空类对象占1字节?
- 示例代码:空类的对象大小
- 输出结果:
- 解释:
- 拓展:含有静态成员的类
- 结论:
- 3. this 指针
- 3.1 什么是 this 指针
- this 指针的内部机制
- 3.2 示例代码
- 解释
- 拓展解释:this指针使用的情况?
- 3.3 this指针的测试题
- 测试题 1:this 指针的基本行为
- 问题描述:
- 解析:
- 正确答案:C. 正常运行
- 测试题 2:this 指针与成员变量的访问
- 问题描述:
- 解析:
- 正确答案:B. 运行时崩溃
- 详细解释:为什么不是空指针访问
- 测试题 3:this 指针的存储位置
- 4. C++和C语言实现Stack的对比
- 4.1 C语言实现Stack
- C语言实现Stack的代码示例
- C语言实现Stack的详细解释
- 4.2 C++语言实现Stack
- C++实现Stack的代码示例
- C++实现Stack的详细解释
- 拓展解释:构造函数和析构函数
- 4.3 C++和C语言实现Stack的对比总结
- 5. 内存对齐
- 5.1 内存对齐规则
- 5.2 示例代码
- 解释
- 拓展解释:为什么需要内存对齐?
- 5.3 内存对齐的影响
- 解释
- 写在最后
C++ 类与对象——详细入门指南
前言
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!
👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!
1. 类的定义
在C++中,类是面向对象编程的基础概念之一。类通过将数据和行为封装在一起,模拟现实世界中的实体。通过类,我们可以定义对象的属性(成员变量)和行为(成员函数)。
1.1 类定义的基本格式
类的定义使用 class
关键字,后面跟上类的名称。在C++中,类体需要用大括号 {}
包裹,并在类定义结束时加上分号 ;
。类中的内容称为类的成员,包括:
- 成员变量:即类的属性,存储类的状态。
- 成员函数:即类的方法,定义类的行为。
示例代码
#include <iostream>
using namespace std;class Stack {
public:// 成员函数:初始化栈void Init(int n = 4) {array = (int*)malloc(sizeof(int) * n);if (nullptr == array) {perror("malloc申请空间失败");return;}capacity = n;top = 0;}// 成员函数:将元素推入栈void Push(int x) {// 如果栈满,进行扩容array[top++] = x;}// 成员函数:获取栈顶元素int Top() {assert(top > 0);return array[top - 1];}// 成员函数:销毁栈void Destroy() {free(array);array = nullptr;top = capacity = 0;}private:// 成员变量:栈的实现细节int* array;size_t capacity;size_t top;
};int main() {Stack st;st.Init();st.Push(1);st.Push(2);cout << st.Top() << endl;st.Destroy();return 0;
}
解释
- class:定义类的关键字。
- Stack:类的名称。
- 成员函数:例如
Init
、Push
、Top
和Destroy
,它们定义了类的行为。 - 成员变量:例如
array
、capacity
、top
,这些变量存储类的状态或属性。 - public/private:访问限定符,决定了类的成员是如何被访问的。
public
成员可以在类外访问,private
成员则只能在类内部使用。
1.2 访问限定符
访问限定符是C++实现封装的方式之一,它决定了类的成员能否被类外部的用户访问。
- public:修饰的成员可以在类外部直接访问。
- private:修饰的成员只能在类内部访问,类外不能直接访问。
- protected:类似于
private
,但在继承中,子类可以访问protected
成员。之后会详细讲解
访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 },即类结束。
示例代码
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 d;d.Init(2024, 3, 31);return 0;
}
解释
- public:声明的
Init
函数是公共的,可以在类外部使用。 - private:
_year
、_month
和_day
是私有成员,不能在类外部直接访问,只有类的成员函数能访问这些变量。
1.3 类域
类的作用域决定了类成员的可访问性。在类外定义成员函数时,需要通过作用域解析符 ::
指明成员属于哪个类。例如,如果在类外定义成员函数,需要使用类名和作用域解析符。
示例代码
#include <iostream>
using namespace std;class Stack {
public:void Init(int n = 4);
private:int* array;size_t capacity;size_t top;
};// 使用作用域解析符在类外定义成员函数
void Stack::Init(int n) {array = (int*)malloc(sizeof(int) * n);if (nullptr == array) {perror("malloc申请空间失败");return;}capacity = n;top = 0;
}int main() {Stack st;st.Init();return 0;
}
解释
- Stack::Init:
::
作用域解析符表示Init
函数属于Stack
类。通过作用域解析符,编译器可以知道该函数属于哪个类,并可以在类的作用域中查找成员变量array
、capacity
和top
。
1.4 成员命名规范
在C++中,通常会为类的成员变量使用特定的命名约定,以避免与函数参数或局部变量混淆。这些命名约定可以提高代码的可读性和维护性。
常见的命名约定:
- 使用下划线
_
:在成员变量的名称前加一个下划线,以区分成员变量和其他变量。 - 使用
m_
前缀:成员变量以m_
开头,表示 “member”(成员变量)。 - 驼峰命名法:使用驼峰命名法,例如
myVariable
,使得代码更易读。
示例:
class Date {
private:int _year; // 使用下划线前缀int m_month; // 使用 m_ 前缀int dayOfMonth; // 使用驼峰命名法
public:void SetDate(int year, int month, int day) {_year = year;m_month = month;dayOfMonth = day;}
};
拓展:
这种命名约定并不是C++语言的强制要求,而是遵循公司或团队的编码风格指南。使用这些命名约定可以避免命名冲突,并让代码的意图更加明确。例如,在 SetDate
函数中,使用 _year
和 year
可以轻松区分成员变量和函数参数,减少混淆。
1.5 class与struct的默认访问权限
在C++中,class
和 struct
的区别主要在于默认的访问权限:
- 在
class
中,未标明的成员变量和成员函数默认是private
。 - 在
struct
中,未标明的成员变量和成员函数默认是public
。
示例:
struct ExampleStruct {int a; // 默认 public
};class ExampleClass {int b; // 默认 private
};
2. 类的实例化
2.1 实例化概念
实例化是指在物理内存中创建对象的过程。类是对象的抽象描述,它提供了对象的结构和行为,但是类本身并不占用物理空间,只有当类被实例化时,才会在内存中分配空间。
示例代码
#include <iostream>
using namespace std;class Date {
public:void Init(int year, int month, int day) {_year = year;_month = month;_day = day;}void Print() {cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main() {Date d1;d1.Init(2024, 3, 31);d1.Print();Date d2;d2.Init(2024, 7, 5);d2.Print();return 0;
}
解释
- Date d1:实例化一个
Date
对象d1
。 - Init:调用
Init
函数初始化d1
对象的成员变量_year
、_month
和_day
。 - Print:输出对象的日期信息。
2.2 对象的大小与存储
当类被实例化为对象时,对象的大小取决于类的成员变量。成员变量需要分配空间,但成员函数不会在对象中存储。成员函数是存储在单独的代码段中的一段指令。
示例代码
#include <iostream>
using namespace std;class A {
private:char _ch; // 1 字节int _i; // 4 字节
};int main() {A a;cout << sizeof(a) << endl; // 输出对象的大小return 0;
}
解释
- 该类的成员变量
_ch
和_i
总共占用 5 字节,但由于内存对齐,实际对象的大小可能是 8 字节。内存对齐规则保证了访问效率(见下文解释)。
2.3 对象大小:空类的情况
在C++中,对象的大小是由类的成员变量决定的,而类的成员函数不会影响对象的大小。因此,如果一个类没有任何成员变量,而只有成员函数,我们称之为空类。这种空类的对象大小在C++中也是有规定的。
空类对象的大小
尽管空类没有成员变量,但在C++中,空类的对象大小仍然不是零。空类的对象大小是1字节。这是因为C++规定每个类的对象都必须占有唯一的地址,即使类中没有成员变量。这1字节的大小用于确保不同的对象在内存中拥有唯一的地址。
为什么空类对象占1字节?
即使类中没有任何成员变量,C++仍然需要给这个对象分配空间,以便它在内存中有一个唯一的地址。这样做有几个目的:
-
区分不同对象的地址:如果类对象占据0字节,那么多个对象可能会共享同一个内存地址,这会导致无法区分不同的对象。因此,C++规定空类对象至少占用1字节的空间。
-
确保类的指针行为一致:即使是空类的指针,指向不同对象时,它们也必须指向不同的内存地址。如果空类对象占0字节,就无法保证这一点。
示例代码:空类的对象大小
#include<iostream>
using namespace std;class EmptyClass {
public:void Print() {cout << "This is an empty class!" << endl;}
};int main() {EmptyClass e1, e2;cout << "EmptyClass 对象 e1 的大小: " << sizeof(e1) << " 字节" << endl;cout << "EmptyClass 对象 e2 的大小: " << sizeof(e2) << " 字节" << endl;return 0;
}
输出结果:
EmptyClass 对象 e1 的大小: 1 字节
EmptyClass 对象 e2 的大小: 1 字节
解释:
- 尽管
EmptyClass
中没有任何成员变量,但每个对象e1
和e2
仍然占用 1 字节。这是为了保证每个对象都有唯一的内存地址,C++通过分配1字节来实现这一点。
拓展:含有静态成员的类
如果一个类只包含静态成员函数或静态成员变量,该类的对象大小依然是1字节。原因是静态成员属于整个类,而不是某个具体的对象,因此静态成员(无论是函数还是变量)不会影响对象的大小。
示例代码:含有静态成员函数和静态成员变量的类
#include<iostream>
using namespace std;class StaticClass {
public:static void StaticPrint() {cout << "This is a static function!" << endl;}static int staticVar; // 静态成员变量
};// 定义并初始化静态成员变量
int StaticClass::staticVar = 10;int main() {StaticClass s1;StaticClass s2;cout << "StaticClass 对象 s1 的大小: " << sizeof(s1) << " 字节" << endl;cout << "StaticClass 对象 s2 的大小: " << sizeof(s2) << " 字节" << endl;// 修改静态成员变量s1.staticVar = 20;cout << "s1.staticVar: " << s1.staticVar << endl;cout << "s2.staticVar: " << s2.staticVar << endl;return 0;
}
输出结果:
StaticClass 对象 s1 的大小: 1 字节
StaticClass 对象 s2 的大小: 1 字节
s1.staticVar: 20
s2.staticVar: 20
解释:
静态成员函数和静态成员变量都属于类,而不是对象。因此,类 StaticClass
中的静态成员函数 StaticPrint
和静态成员变量 staticVar
都不影响对象的大小。
对象 s1
和 s2
的大小依然为 1 字节,静态成员变量在类的所有对象中共享,只占用一份内存。
结论:
- 空类对象的大小是1字节,即使类中没有成员变量,C++仍然会为每个空类对象分配1字节的空间,以确保每个对象有唯一的内存地址。
- 静态成员不影响对象的大小,因为它们属于类,而不是对象,空类或仅含有静态成员函数或者变量的类对象同样占用1字节。
3. this 指针
3.1 什么是 this 指针
this
是C++中的一个隐式指针,存在于每一个非静态成员函数中。this
指针指向调用该成员函数的当前对象。在类的成员函数中,this
指针可以用来访问类的成员变量和成员函数。
- 当我们在成员函数中使用类的成员变量时,本质上是通过
this
指针访问该成员变量。 this
指针是隐含的,C++自动将它传递给所有非静态成员函数。
this 指针的内部机制
在C++中,当类的成员函数被调用时,this
指针会被自动传递给该函数。this
指针的类型是 const Type* const
,它是一个只读指针,指向当前对象。你不能修改 this
指针的值,但可以通过 this
访问对象的成员。
3.2 示例代码
#include<iostream>
using namespace std;class Date {
public:void Init(int year, int month, int day) {this->_year = year; // 通过 this 指针访问成员变量this->_month = month;this->_day = day;}void Print() {cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main() {Date d1;d1.Init(2024, 3, 31);d1.Print();return 0;
}
解释
- this:在
Init
函数中,this->_year = year
表示将传入的参数year
赋值给当前对象的_year
成员变量。this
指针指向当前调用Init
函数的对象(即d1
)。
拓展解释:this指针使用的情况?
在通常的情况下,编写代码时我们不需要显式使用 this
指针,因为C++会自动处理类的成员和参数之间的冲突。但是在某些情况下,例如当函数的参数名称和成员变量名称相同时,使用 this
指针可以明确地表示成员变量。
void Init(int year, int month, int day) {this->year = year; // 通过 this 指针区分成员变量和函数参数this->month = month;this->day = day;
}
在上面的代码中,如果不使用 this
,编译器会把 year
解释为函数参数,而不是成员变量 的year
。这就导致了未定义的行为,成员变量没有被正确的赋值。
所以也进一步说明了我们在类里面定义的成员变量需要有一种命名规范,笔者习惯用
_
作为前缀
3.3 this指针的测试题
测试题 1:this 指针的基本行为
#include<iostream>
using namespace std;class A {
public:void Print() {cout << "A::Print()" << endl;}
private:int _a;
};int main() {A* p = nullptr;p->Print(); // 能否正常运行?return 0;
}
问题描述:
我们定义了一个 A
类,包含一个成员函数 Print()
,它只是输出一条字符串。我们通过空指针 p
调用 Print()
函数,询问这段代码能否正常运行。
解析:
- 在
main()
函数中,A* p = nullptr;
创建了一个指向A
类的空指针p
。 - 随后我们通过空指针
p->Print()
来调用成员函数Print()
,这里的this
指针其实是空指针。
为什么不会报错?
- 当我们调用
p->Print()
时,this
指针实际上等于nullptr
,但由于Print()
函数没有访问任何成员变量,因此C++允许这个调用。 this
指针是隐含的,虽然在函数内部会传递this
,但是如果成员函数不访问任何成员变量,C++不需要解引用这个空指针,因此不会出现空指针访问的错误。Print()
函数只是输出了一段固定的文本,不涉及对象的状态或成员变量,因此即使this
是空指针,也不会导致问题。
正确答案:C. 正常运行
测试题 2:this 指针与成员变量的访问
#include<iostream>
using namespace std;class A {
public:void Print() {cout << "A::Print()" << endl;cout << _a << endl;}
private:int _a;
};int main() {A* p = nullptr;p->Print(); // 能否正常运行?return 0;
}
问题描述:
这段代码和前一个问题类似,但在 Print()
函数中多了一个成员变量 _a
的访问。通过空指针 p
调用 Print()
,是否会出现空指针访问的问题?
解析:
- 在
main()
中,和之前一样,A* p = nullptr;
创建了一个指向A
类的空指针p
。 - 当调用
p->Print()
时,this
指针仍然是nullptr
。
为什么会崩溃?
- 在
Print()
函数中,除了输出A::Print()
以外,代码还试图访问类的成员变量_a
。 - 当
this
指针为nullptr
时,访问this->_a
等同于尝试通过空指针访问成员变量。这是一种未定义行为,在大多数系统中会导致程序崩溃。 - 成员变量
_a
存储在对象的内存空间中,而通过空指针访问成员变量时,由于没有实际的对象空间可用,因此程序在运行时会发生崩溃。
正确答案:B. 运行时崩溃
详细解释:为什么不是空指针访问
在这两个测试题中,关键点在于是否访问了成员变量:
-
测试题 1 中,
Print()
函数没有访问成员变量,所以即使this
是空指针,C++也不会触发空指针访问错误。这是因为成员函数本质上只是一个在内存中的函数指针,调用它并不一定需要访问实际对象的内存。 -
测试题 2 中,
Print()
函数试图访问成员变量_a
。由于成员变量存储在对象的内存空间中,而空指针this
并没有指向有效的内存区域,所以在运行时会试图通过空指针访问内存,导致程序崩溃。这是典型的空指针访问错误。
总结来说,空指针调用成员函数本身并不会报错,因为成员函数本来就不在类中,所以不是解引用,编译时的汇编代码这里就只是一段函数的地址而已,只是这里没有对象,传过去的this指针就是空指针,但只要该成员函数不涉及访问成员变量或其他依赖对象内存的操作那就不会报错。然而,一旦成员函数试图通过
this
指针访问成员变量,程序就会崩溃,因为this
为nullptr
,没有有效的内存空间可供访问。
测试题 3:this 指针的存储位置
选择题:this 指针存在于内存的哪个区域?
- A. 栈
- B. 堆
- C. 静态区
- D. 常量区
- E. 对象里
正确答案:A. 栈
解释:
this
指针作为成员函数的一个隐含参数,存储在栈中。每当一个成员函数被调用时,this
指针会作为函数参数被压入栈中。
this指针其实就是函数的参数而已
4. C++和C语言实现Stack的对比
接下来我们通过C和C++语言实现 Stack
栈的对比,展现C++相较于C语言在封装和安全性方面的优势。
4.1 C语言实现Stack
在C语言中,实现 Stack
需要使用 struct
来定义栈的数据结构,并且所有的栈操作(如初始化、压栈、弹栈等)都通过独立的函数实现。由于C语言不支持面向对象编程,数据和操作必须分开处理,所有栈操作的函数需要手动传递 Stack
结构体的指针作为参数。
C语言实现Stack的代码示例
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int STDataType;
typedef struct Stack {STDataType* array;int top;int capacity;
} Stack;void Init(Stack* s) {s->array = (STDataType*)malloc(4 * sizeof(STDataType));s->capacity = 4;s->top = 0;
}void Destroy(Stack* s) {free(s->array);s->array = NULL;s->top = 0;s->capacity = 0;
}void Push(Stack* s, STDataType x) {if (s->top == s->capacity) {s->capacity *= 2;s->array = (STDataType*)realloc(s->array, s->capacity * sizeof(STDataType));}s->array[s->top++] = x;
}STDataType Top(Stack* s) {assert(s->top > 0);return s->array[s->top - 1];
}void Pop(Stack* s) {assert(s->top > 0);--s->top;
}bool Empty(Stack* s) {return s->top == 0;
}int main() {Stack s;Init(&s);Push(&s, 1);Push(&s, 2);printf("栈顶元素: %d\n", Top(&s));Pop(&s);printf("栈顶元素: %d\n", Top(&s));Destroy(&s);return 0;
}
C语言实现Stack的详细解释
-
结构体
Stack
:这是一个结构体,包含了三个成员:array
:一个指向栈的动态数组的指针,用来存储栈中的元素。top
:指向栈顶元素的指针,它代表当前栈中元素的个数。capacity
:栈的容量,表示栈中最多可以容纳的元素个数。
-
函数
Init
:用于初始化栈的大小,并为数组分配内存。C语言中没有构造函数,因此必须通过函数显式初始化结构体。 -
函数
Push
:将元素压入栈中,如果栈满则进行扩容操作,使用realloc
函数为栈分配更大的内存。 -
函数
Top
:返回栈顶元素,调用时需要确保栈不为空。 -
函数
Pop
:将栈顶元素弹出,减少top
的值。 -
内存管理:通过
malloc
和realloc
动态分配内存,使用free
释放内存。
4.2 C++语言实现Stack
C++通过类的封装,可以将数据和操作放在一起。栈的实现不仅更为简洁,而且通过封装性提高了代码的安全性和可维护性。相比C语言,C++不需要手动传递 Stack
指针,而是通过类的成员函数自动操作栈。
C++实现Stack的代码示例
#include<iostream>
using namespace std;class Stack {
public:Stack(int n = 4) {_array = (int*)malloc(sizeof(int) * n);_capacity = n;_top = 0;}~Stack() {free(_array);_array = nullptr;}void Push(int x) {if (_top == _capacity) {_capacity *= 2;_array = (int*)realloc(_array, _capacity * sizeof(int));}_array[_top++] = x;}int Top() {assert(_top > 0);return _array[_top - 1];}void Pop() {assert(_top > 0);--_top;}bool Empty() {return _top == 0;}private:int* _array;size_t _capacity;size_t _top;
};int main() {Stack s;s.Push(1);s.Push(2);cout << "栈顶元素: " << s.Top() << endl;s.Pop();cout << "栈顶元素: " << s.Top() << endl;return 0;
}
C++实现Stack的详细解释
-
构造函数和析构函数:
- 构造函数
Stack(int n = 4)
:用于初始化栈,分配内存并设置栈的容量。这里的n
是栈的初始大小,默认值为4。构造函数在对象创建时自动调用,确保对象处于有效的状态。 - 析构函数
~Stack()
:当栈对象被销毁时,自动释放动态分配的内存。这是C++相比C语言的一个显著优势,因为不需要手动调用Destroy
函数来释放资源。
- 构造函数
-
成员函数
Push
:与C语言中的Push
函数类似,用于将元素压入栈中。栈满时会自动扩容,但通过成员函数的封装,这一操作对类外的用户是透明的,用户只需要调用Push
方法即可。 -
成员函数
Top
:返回栈顶元素,和C语言一样,操作之前会检查栈是否为空,保证操作的安全性。 -
封装性:相比C语言,C++通过类的
private
成员变量_array
、_capacity
和_top
,将栈的实现细节封装起来,防止用户直接操作这些数据。所有的操作都通过public
成员函数完成,保证了数据的安全性。
拓展解释:构造函数和析构函数
(在下一篇博客会详细解释)
-
构造函数:它是类中的特殊函数,当类的对象被创建时,构造函数会被自动调用,用于初始化对象。在
Stack
类中,构造函数初始化栈的容量,并为数组分配内存。 -
析构函数:它也是类中的特殊函数,当对象生命周期结束(如对象作用域结束时)时,析构函数会自动调用,用于释放对象所占用的资源。在
Stack
类中,析构函数用于释放malloc
分配的内存,避免内存泄漏。
4.3 C++和C语言实现Stack的对比总结
通过对C和C++实现 Stack
的对比,可以得出以下几点总结:
-
封装性:C++通过类的封装将数据和操作整合在一起,类的用户不需要关心栈的实现细节,而C语言的实现则需要用户手动调用函数并管理结构体的状态。
-
内存管理:C语言中,内存管理是手动的,开发者必须显式调用
free
函数释放内存。而在C++中,析构函数自动负责资源的释放,避免了忘记释放内存导致的内存泄漏问题。 -
安全性:C++中的类通过
private
关键字保护类的内部数据,防止外部代码随意修改类的成员变量,增强了数据的安全性。而C语言没有这种封装机制,所有数据都可以通过结构体直接访问,容易导致意外的修改和错误。
5. 内存对齐
内存对齐是计算机系统中用于优化数据访问的机制。在C++中,类的成员变量在内存中的存放位置要遵循特定的对齐规则,以提高CPU的访问效率。
这部分内容在C语言结构体详细介绍过,详情请见【C语言篇】结构体和位段详细介绍
5.1 内存对齐规则
内存对齐规则规定:
-
第一个成员存储在偏移量为0的地址处。
-
其他成员必须存储在某个对齐数的整数倍的地址处。
-
对齐数取决于编译器的设置和变量的类型,通常为4字节或8字节。
5.2 示例代码
#include<iostream>
using namespace std;class A {
private:char _ch; // 1 字节int _i; // 4 字节
};int main() {A a;cout << "对象 A 的大小: " << sizeof(a) << " 字节" << endl; // 输出对象大小return 0;
}
解释
尽管 A
类中的 _ch
和 _i
变量加起来只有 5 字节,但由于内存对齐规则,类的实际大小为 8 字节。这是因为 int
变量 _i
要求4字节对齐,而 char
只占1字节,因此在 char
后面会插入3个字节的填充空间,以便 int
对齐到4字节边界。
拓展解释:为什么需要内存对齐?
-
内存对齐的意义:内存对齐的目的是为了优化CPU的读取速度。在现代计算机架构中,处理器一次性读取的内存块大小通常为4字节或8字节。如果数据存储在不对齐的地址上,处理器需要执行更多的操作来读取数据,从而影响性能。因此,通过对齐存储,处理器可以更快速地读取和写入数据。
-
内存对齐和空间浪费:虽然内存对齐提高了数据访问的效率,但也可能会造成空间浪费。例如,在上例中,
char
类型只需要1字节,但为了对齐int
,额外浪费了3个字节的填充空间。这种权衡在性能优化和内存空间利用之间找到了平衡。
5.3 内存对齐的影响
内存对齐会影响类的实际大小。例如,以下代码展示了不同对齐方式下对象的大小变化:
#include<iostream>
#pragma pack(1) // 设置内存对齐为1字节
using namespace std;class B {
public:char _ch; // 1字节int _i; // 4字节
};int main() {B b;cout << "对象 B 的大小: " << sizeof(b) << " 字节" << endl; // 输出对象 B 的大小为 5 字节return 0;
}
#pragma pack() // 恢复默认对齐
解释
通过使用 #pragma pack(1)
指令,我们将类 B
的内存对齐设置为 1 字节对齐。这样,char
类型占用 1 字节,int
类型占用 4 字节,总共5字节,没有插入额外的填充字节。
写在最后
在这篇文章中,我们深入探讨了C++中类与对象的基础概念。我们从类的定义开始,讲解了类的封装、成员变量和成员函数的区别与作用,介绍了类的访问限定符及其影响。同时,我们讨论了对象的实例化及其内存存储机制,包括了空类对象的内存占用问题。通过this指针的机制,我们了解了C++中对象访问成员的内部逻辑。最后,我们对比了C++和C语言实现Stack的方式,并讲解了内存对齐的规则及其影响。希望这些内容能帮助你更好地理解C++中的类与对象这一核心概念。
以上就是关于C++类与对象深度解析(一):从抽象到实践的全面入门指南的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️
相关文章:

C++类与对象深度解析(一):从抽象到实践的全面入门指南
文章目录 C 类与对象——详细入门指南前言1. 类的定义1.1 类定义的基本格式示例代码解释 1.2 访问限定符示例代码解释 1.3 类域示例代码解释 1.4 成员命名规范常见的命名约定:示例:拓展: 1.5 class与struct的默认访问权限示例: 2.…...

docker拉取 jdk 8
docker pull openjdk:8docker run -d -it --name java-8 openjdk:8docker run -d -it --name java-8 openjdk:8 –name java-8 容器名,自定义的 openjdk:8 镜像名:标签名 , 使用 docker images 查看 2、查看已运行的容器实例: doc…...

机器学习VS深度学习
机器学习(Machine Learning, ML)和深度学习(Deep Learning, DL)是人工智能(AI)的两个子领域,它们有许多相似之处,但在技术实现和应用范围上也有显著区别。下面从几个方面对两者进行区…...

基于vue框架的宠物交流平台1n2n3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:会员,宠物信息,宠物类型,团队信息,申请领养,团队申请,领养宠物 开题报告内容 基于Vue框架的宠物交流平台开题报告 一、项目背景 随着现代生活节奏的加快与人们情感需求的日益增长,宠物已成为众多家庭不可或缺的重要成员。…...

Rust 所有权 借用与引用
文章目录 发现宝藏1. 所有权(Ownership)2. 引用(References)2.1 不可变引用2.2 可变引用2.3 引用的规则 3. 悬垂引用(Dangling References)4. 借用(Borrowing)结论 发现宝藏 前些天…...

构建智能电商新生态:深度解析京东商品详情API的力量
在当今数字化浪潮中,智能电商系统已成为推动零售业转型升级的重要引擎。作为电商行业的领军者之一,京东凭借其庞大的商品数据库和先进的技术架构,为开发者与商家提供了丰富的API接口,其中商品详情API无疑是构建智能电商系统的关键…...

Golang | Leetcode Golang题解之第398题随机数索引
题目: 题解: type Solution []intfunc Constructor(nums []int) Solution {return nums }func (nums Solution) Pick(target int) (ans int) {cnt : 0for i, num : range nums {if num target {cnt // 第 cnt 次遇到 targetif rand.Intn(cnt) 0 {ans …...

使用注意力机制可以让你的模型更加灵活,但是需要额外的计算资源。rnn lstm bilstm attension
确实,使用注意力机制可以使模型更加灵活,但也确实需要额外的计算资源。注意力机制允许模型在处理序列数据时,能够动态地关注不同位置的重要性,从而更好地捕捉长依赖关系。下面是一个简单的注意力机制实现示例,可以帮助…...

git命令大全
简介:个人学习分享,如有错误,欢迎批评指正 一、Git操作流程 1、代码提交和同步代码 第零步: 工作区与仓库保持一致第一步: 文件增删改,变为已修改状态第二步: git add ,变为已暂存状态 $ git status $ git add --al…...

【数据仓库】数据仓库常见的数据模型——范式模型
目录 一、范式 1、第一范式 2、第二范式 3、第三范式 4、进一步范式化:BCNF、4NF 和 5NF 简介 (1)Boyce-Codd 范式(BCNF) (2)第四范式(4NF) (5&#x…...

【LeetCode每日一题】——LCR 078.合并 K 个升序链表
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目注意】六【题目示例】七【题目提示】八【解题思路】九【时间频度】十【代码实现】十一【提交结果】 一【题目类别】 优先队列 二【题目难度】 困难 三【题目编号】 LCR 078.合并 K 个升序链表 …...

代码随想录算法训练营第五十九天 | dijkstra(堆优化版)精讲
目录 dijkstra(堆优化版)精讲 思路 堆优化细节 方法一: 最小堆优化 dijkstra(堆优化版)精讲 题目链接:卡码网:47. 参加科学大会 文章讲解:代码随想录 小明是一位科学家&#x…...

go语言后端开发学习(七)——如何在gin框架中集成限流中间件
一.什么是限流 限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。 我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为8万人;沙河地铁站早高峰通过站外排队逐一放行的…...

SpringBoot2:web开发常用功能实现及原理解析-整合EasyExcel实现Excel导入导出功能
1、工程包结构 主要是这5个Java类 2、导入EasyExcel包 这里同时贴出其他相关springboot的基础包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><depend…...

CTFShow-信息搜集
Web1: 题目描述:开发注释未及时删除 。 打开题目后提示web1:where is flag? ctrlu读取源码。 Web2: 题目描述:js前台拦截 无效操作 打开题目后显示:无法查看源代码 右键无法用,…...

Facebook的虚拟现实功能简介:社交网络的新前沿
在科技飞速发展的今天,虚拟现实(VR)已经从科幻小说中的梦想变成了触手可及的现实。作为全球领先的社交平台,Facebook(现已更名为Meta)正大力推动虚拟现实技术的发展,以重新定义用户的社交体验。…...

Redis embstr 编码
embstr 编码 是 Redis 中一种优化存储小型字符串的编码方式。它是 Redis 内部存储字符串的多种方式之一,特别适用于存储长度不超过 44 字节的小字符串。...

【Elasticsearch系列二】安装 Kibana
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

中国电子学会202403青少年软件编程(Python)等级考试试卷(三级)真题与解析
202403Python 三级真题 一、选择题 1.在 Python 中,hex(2023)的功能是?( ) A.将十进制数 2023 转化为十六进制数 B.将十进制数 2023 转化为八进制数 C.将十六进制数 2023 转化为十进制数 D.将八进制数 2023 转化为十进制数 2.下列表达式的值与其他三个选项不相…...

k8s 资源管理
文章目录 ResourceQuota什么是资源配额定义一个ResourceQuotaResourceQuota的使用 LimitRangeLimitRange的用途示例1:配置默认的requests和limits示例2:配置requests和limits的范围 QoS什么是服务质量保证示例1:实现QoS为Guaranteed的Pod示例…...

演示:基于WPF的自绘的中国地铁轨道控件
一、目的:演示一个基于WPF的自绘的中国地铁轨道控件 二、效果演示 北京地铁 成都地铁 上海地铁 深圳地铁 南京地铁 长春地铁 哈尔滨地铁 武汉地铁 厦门地铁 香港地铁 三、功能 支持平移、缩放等操作 鼠标悬停显示线路信息和站点信息 按表格显示,按纸张…...

设计模式(Design Patterns)
设计模式(Design Patterns)是软件开发人员在软件设计过程中面临的一般性问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式的目的是为了提高代码的可重用性、可维护性、可读性、可靠性以及灵活性。设…...

C++:opencv生成结构元素用于膨胀腐蚀等cv::getStructuringElement
cv::getStructuringElement 是 OpenCV 库中用于生成结构元素的函数。结构元素在形态学操作中(如膨胀、腐蚀、开运算、闭运算等)扮演着关键角色。这个函数可以创建不同形状和尺寸的结构元素,以适应不同的图像处理需求。 函数原型 cv::Mat cv…...

最大余额法,解决百分比计算相加不等于100%(扇形/饼图百分比使用的此算法)
在开发项目的过程中有时候需要进行计算百分比,例如计算饼状图百分比。有时候在计算的过程中常规四舍五入计算会发生所有计算的值相加不等于100%的情况 这是 get_percent_value 函数的 JavaScript 版本: /*** 最大余额法,解决百分比计算相加不…...

哈希表简单介绍
概念 在顺序结构以及平衡树中,元素关键字与他们存储的位置并没有直接的映射关系,从而会影响查找关键字的效率,顺序结构中查找关键字的时间复杂度为O(N),平衡树查找关键字的时间复杂度为O(log2^…...

kafka 之 本地部署单机版
安装JDK 查看你选择的版本需要安装哪一个版本的jdk 网址 下载 JDK下载 注:如果网页不允许下载,使用wget命令下载即可,下载之后安装。 建议使用rpm安装,之后使用 update-alternatives --config java 控制当前环境使用Java的版…...

开发一款通过蓝牙连接控制水电表的微信小程序
增强软硬件交互 为了更好的解决师生生活中的实际问题,开发蓝牙小程序加强了和校区硬件的交互。 比如通过蓝牙连接控制水电表,减少实体卡片的使用。添加人脸活体检测功能,提高本人认证效率,减少师生等待时间。 蓝牙水电控展示 蓝…...

力扣14.最长公共前缀
思路:将字符串数组中第一个字符串用作参考; 8.将他的长度作为范围,因为超范围了之后就不会再有公共前缀了 9.将字符串数组的长度也作为范围,意思是便利字符串数组中的字符串 11.开始第一层循环,依次遍历第一个字符串的…...

你也喜欢“钓鱼“吗?
免责声明:本文仅做分享! 目录 什么是网络钓鱼 流程 攻击手法 0-隐藏自己 1-office宏 创建xxx.dotm 创建xxx.docx 2-RLO 自解压 3-快捷方式lnk 4-邮件伪造 Swaks Gophish 5-网站克隆 setoolkit nginx反向代理 前端页面克隆 6-wifi钓鱼 7-其他 防御 溯源 反…...

druid jdbc 执行 sql 输出 开销耗时
druid 执行sql输出 参考链接配置_LogFilter alibaba/druid Wiki GitHub 看不太懂的往这里瞅瞅。 1. 别名映射 这个地方 给我们提供了 5 种 logfilter : log4j、log4j2、slf4j、commonlogging和commonLogging 每一种实际上都代表一个日志框架 或 日志门面。 -Ddruid.fil…...