大家好,我是Linux兵工厂,在工作经常发现小伙伴们遇到一些C++的问题都是对基础知识不熟悉或理解混乱所导致的。正所谓万丈高楼平地起,作为一名合格的程序员来说,没有良好的基本功很难达到一定的高度。而工作中大部分编程问题都是基本功不扎实所导致,所以决定花些时间来整理C++相关的基本知识和基本概念供大家参考理解,每一个知识点都结合相关的代码进行验证。本文基本上涵盖了C++最常用的知识点,希望对小伙伴们有所帮助。关注公众号:Linux兵工厂,领取海量Linux免费学习资料,且会不定时输出更多干货知识
1. volatile
在C++中,volatile是一个关键字,用于修饰变量,告诉编译器该变量的值可能在程序流程之外被意外修改,因此编译器不应该对该变量进行优化(如缓存变量值或重排指令顺序)。
volatile主要用于以下场景:
1、多线程访问共享变量:在多线程编程中,如果一个变量被多个线程访问,并且其中一个线程可能会修改该变量的值,就应该使用volatile修饰该变量,以确保线程能够正确读取变量的最新值,而不是从缓存中读取旧值。
2、中断处理:在嵌入式系统或硬件相关的编程中,中断处理程序中通常会访问硬件寄存器或其他与硬件相关的状态变量。由于中断处理程序可能在程序的正常流程之外执行,为了确保正确处理这些变量,应使用volatile修饰。
以下是一个简单的示例,演示了volatile的用法:
#include <iostream>
#include <thread>
volatile int sharedVariable = 0;
void modifySharedVariable() {
for (int i = 0; i < 5; ++i) {
sharedVariable = i;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void readSharedVariable() {
for (int i = 0; i < 5; ++i) {
std::cout << "Read sharedVariable: " << sharedVariable << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
int main() {
std::thread writerThread(modifySharedVariable);
std::thread readerThread(readSharedVariable);
writerThread.join();
readerThread.join();
return 0;
}
在上述示例中,我们使用了volatile修饰sharedVariable变量。modifySharedVariable()函数在循环中不断修改sharedVariable的值,而readSharedVariable()函数在另一个线程中循环读取sharedVariable的值。由于sharedVariable是一个共享变量,在多线程环境下,为了避免读取旧值,我们使用volatile修饰,确保readSharedVariable()函数能够正确读取到最新的值。
需要注意的是,volatile修饰符只用于修饰变量,而不是函数。它不会解决所有多线程问题,更复杂的线程同步问题可能需要使用互斥锁(std::mutex)或其他同步机制来保证正确性。
2. assert()
在C++中,assert()是一个宏定义,用于在代码中进行断言检查。它是一个调试工具,用于在程序运行时检查某个条件是否为真。如果断言条件为假(即false),则会触发断言失败,并导致程序中止执行。在发布版本中,默认情况下,断言会被禁用,因此不会对性能产生影响。
assert()宏的定义位于<cassert>头文件中,通常在开发阶段使用,以帮助开发者检测程序中的错误和问题。在调试阶段,当断言条件为假时,它会输出错误信息,并在终端显示断言失败的位置和原因。
断言的一般语法如下:
#include <cassert>
int main() {
int x = 10;
assert(x == 5); // 断言条件为假,程序会终止,并显示错误信息
return 0;
}
在上述代码中,assert(x == 5)会检查变量x是否等于5。由于x的值为10,断言条件为假,程序会终止执行,并显示断言失败的信息,如文件名、行号、条件表达式等。
需要注意的是,由于在发布版本中默认会禁用断言,因此不应该将assert()用于对用户输入进行验证或执行关键业务逻辑。对于这些情况,应该使用更稳健的错误处理机制。
在开发过程中,合理使用assert()可以帮助发现代码中的问题,提高程序的健壮性和可维护性。但在最终发布版本中,需要确保去除所有不必要的断言,以确保代码的性能和正确性。
3. sizeof()
在C++中,sizeof是一个运算符,用于计算类型或变量的大小(字节数)。它的语法形式为sizeof (type)或sizeof expression。
运算符有以下几个特点和使用场景:
1. 返回值:sizeof运算符返回一个size_t类型的值,表示类型或变量所占用的字节数。
2. 对类型的大小计算:对于给定的数据类型,sizeof(type)可以计算出该类型的大小。例如:
#include <iostream>
int main() {
std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;
return 0;
}
输出可能为:
Size of int: 4 bytes
Size of double: 8 bytes
- 对变量的大小计算:sizeof运算符也可以计算变量所占用的字节数。例如:
#include <iostream>
int main() {
int x = 10;
double y = 3.14;
std::cout << "Size of x: " << sizeof(x) << " bytes" << std::endl;
std::cout << "Size of y: " << sizeof(y) << " bytes" << std::endl;
return 0;
}
输出可能为:
Size of x: 4 bytes
Size of y: 8 bytes
- 对数组的大小计算:对于数组,sizeof运算符可以计算整个数组所占用的总字节数。例如:
#include <iostream>
int main() {
int arr[] = {1, 2, 3, 4, 5};
std::cout << "Size of arr: " << sizeof(arr) << " bytes" << std::endl;
return 0;
}
输出可能为:
Size of arr: 20 bytes
- 对指针的大小计算:`sizeof`运算符计算指针变量本身的大小,而不是指针所指向的对象的大小。无论指针指向的对象类型大小是多少,指针本身的大小都是固定的。
#include <iostream>
int main() {
int x = 10;
int* ptr = &x;
std::cout << "Size of ptr: " << sizeof(ptr) << " bytes" << std::endl;
return 0;
}
输出可能为:
Size of ptr: 8 bytes (在 64 位系统上)
请注意,sizeof运算符在编译时计算,不会真正运行代码。因此,它在编译时就能知道类型或变量的大小,并返回一个常量值。
总之,sizeof运算符是一个非常有用的工具,用于在编程中确定数据类型和变量的大小,特别是在处理内存分配、结构体、数组等场景中。
4. #pragma pack(n)
在C++中,#pragma pack(n)是一个预处理指令(preprocessor directive),用于告诉编译器按照指定的字节对齐方式对结构体或类进行内存对齐。通常情况下,编译器会对结构体或类进行自动的内存对齐,以提高访问效率和性能。
#pragma pack(n)的语法中,n是指定的对齐字节数,可以是1、2、4、8等,表示结构体或类的成员变量将按照n字节对齐。在结构体或类定义之前使用该预处理指令,其作用会影响接下来的结构体或类的成员排列。
以下是一个简单的示例,演示了#pragma pack(n)的用法:
#include <iostream>
// 默认情况下,编译器会进行自动对齐,对于int类型通常是4字节对齐
struct MyStructAuto {
char c;
int i;
};
// 使用 #pragma pack(1) 指定1字节对齐,取消自动对齐
#pragma pack(1)
struct MyStructPacked {
char c;
int i;
};
#pragma pack()
int main() {
std::cout << "sizeof(MyStructAuto): " << sizeof(MyStructAuto) << std::endl;
std::cout << "sizeof(MyStructPacked): " << sizeof(MyStructPacked) << std::endl;
return 0;
}
输出可能为:
sizeof(MyStructAuto): 8
sizeof(MyStructPacked): 5
在上述示例中,我们定义了两个结构体:MyStructAuto和MyStructPacked。在MyStructAuto中,编译器会自动进行对齐,默认情况下,int类型通常是4字节对齐,因此MyStructAuto的大小是8字节(1字节的char加上4字节的int,再加上3字节的填充)。
而在MyStructPacked中,我们使用了#pragma pack(1)指定了1字节对齐,这将取消自动对齐,导致MyStructPacked的大小只有5字节(1字节的char加上4字节的int,没有填充字节)。
需要注意的是,使用#pragma pack(n)可能会影响内存对齐,导致结构体或类的访问效率降低,尤其是对于大型结构体。在使用#pragma pack(n)时,应谨慎考虑,确保了解其影响,并只在必要时使用。通常情况下,让编译器自动进行内存对齐是较为推荐的做法。