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示例…...
LangChainJS设计模式:可复用AI组件的架构思想
LangChainJS设计模式:可复用AI组件的架构思想 【免费下载链接】langchainjs 项目地址: https://gitcode.com/GitHub_Trending/la/langchainjs LangChainJS是一个用于构建LLM驱动应用程序的JavaScript/TypeScript框架,它通过可复用AI组件和设计模…...
GitHub访问加速终极指南:5分钟告别龟速访问的完整解决方案
GitHub访问加速终极指南:5分钟告别龟速访问的完整解决方案 【免费下载链接】fetch-github-hosts 🌏 同步github的hosts工具,支持多平台的图形化和命令行,内置客户端和服务端两种模式~ | Synchronize GitHub hosts tool, support m…...
OpenClaw多用户方案:QwQ-32B共享环境下的权限隔离
OpenClaw多用户方案:QwQ-32B共享环境下的权限隔离 1. 为什么需要多用户方案? 去年我在家里搭建了一个OpenClaw自动化环境,原本只是个人使用。直到某天家人看到我用语音指令让AI自动整理照片、生成周报后,纷纷要求"共享&quo…...
MATLAB实战:手把手教你实现FM调制解调(附完整代码与避坑指南)
MATLAB实战:从零构建FM通信系统的完整指南 在无线通信领域,频率调制(FM)技术因其出色的抗噪声性能,至今仍广泛应用于广播、对讲机等场景。对于通信工程学生和MATLAB初学者而言,亲手实现一个完整的FM调制解调系统,是理解…...
热门编程语言全攻略:从入门到职业选手
目录 引言:为什么选择一门“热门”编程语言 1.1 编程语言热度背后的产业逻辑 1.2 初学者如何选择第一门语言 1.3 全栈/进阶者如何扩展技术栈 Python:万能胶水与人工智能首选 2.1 语言定位与核心应用领域 2.2 语法特点:简洁优雅的伪代码 2.3 学…...
1Panel新手必看:5分钟搞定RustDesk远程桌面搭建(含端口配置避坑指南)
1Panel极速部署RustDesk:零基础构建安全远程桌面的完整指南 当我们需要远程管理Linux服务器时,一个轻量级、开源的远程桌面解决方案往往比商业软件更灵活可控。RustDesk作为新兴的远程工具,凭借其跨平台特性和自建服务器的能力,正…...
别再只调库了!拆解一个智能家居语音项目,聊聊STM32裸机开发中多任务处理的几种实用思路
裸机开发的艺术:STM32智能家居项目中多任务处理的五种高阶策略 从智能家居项目看裸机开发的挑战与机遇 在嵌入式开发领域,RTOS(实时操作系统)的普及让许多开发者形成了思维定式——面对多任务需求时,第一反应往往是移植…...
机器人离线编程专访:我是SiemensMCD与pdps用户,该不该切换为国产机器人设计与仿真软件iRobotCAM
摘要: 作为Siemens MCD与PDPS的用户,我从PDPS切换到其它软件时会考虑哪些因素,该不该切换到国产的iRobotCAM,本文通过专该机器人设计与仿真软件专家的形式,提供行业从业者的视角,阐述iRobotCAM的产品特点与适用性。工业…...
RAR Unlocker 4.0 汉化版:专注 RAR 压缩包锁定 / 解锁,支持查看属性与命令行批量处理,轻量便携,是解决 RAR 锁定问题的优质辅助工具
大家好,我是大飞哥。日常使用 RAR 压缩包时,误操作锁定后会导致文件无法修改、添加或删除,而 WinRAR 本身又不提供便捷的解锁功能,手动处理不仅繁琐还容易损坏压缩包 —— 而RAR Unlocker 4.0 汉化版就是专为解决这些痛点打造的轻…...
手把手教你学Simulink——基于Simulink的同步整流Buck变换器效率提升仿真
目录 手把手教你学Simulink——基于Simulink的同步整流Buck变换器效率提升仿真 摘要 一、背景与挑战 1.1 传统二极管整流的效率瓶颈 1.1.1 二极管损耗机理 1.2 同步整流的优势与挑战 1.2.1 同步整流原理 1.2.2 核心挑战 1.3 设计目标 二、系统架构与…...
