基本概念¶
指针的大小?¶
在 32 位操作系统中,指针的大小是 4 个字节 在 64 位操作系统中,指针的大小是 8 个字节
因为地址是 32 位的,8 位一个字节
但是,位数低的老 CPU 和嵌入式 CPU 情况不同
8086 就因为其分段的内存模型,引入了三种不同的指针:
- Near,大小为 16 位
- Far,大小为 32 位(段 + 偏移),不进行规范化,指向的数据结构不能跨段
- Huge,大小为 32 位(段 + 偏移),有规范化:这种指针的算术运算需要特殊实现来支持跨段的大型数据结构
32位系统下 int, float, long 占多少字节?¶
https://zh.cppreference.com/w/cpp/language/types
什么情况下会使用静态变量?¶
- 静态全局变量和静态函数的作用域只在当前文件中,解决了文件之间的符号污染问题
- 局部静态变量,将栈变量生命周期延长到程序执行结束时。在离开当前作用域后,再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。 这么做的用意在于,对于某些局部变量,我们可以保留并使用一些需要的信息,比如记录这个函数被调用了多少次。
- 类的静态成员可以实现多个对象之间的数据共享
- 类的静态函数和静态数据成员一样,它们都属于类的静态成员,而不是某个具体对象成员,因此,对于静态成员的调用不需要用对象名
讲一讲 extern "C"¶
extern "C" 在 C++ 中是一个特殊的链接规范,用于确保 C++ 代码可以与C代码进行互操作。以下是关于extern "C"的一些注意点:
-
名称修饰(Name Mangling):
- 当 C++ 编译器编译函数时,为了支持函数重载(即允许多个函数有相同的名字但参数类型不同),它会对函数名进行所谓的“名称修饰”或“名称改编”。
- C 编译器不支持函数重载,因此不进行名称修饰。
extern "C"告诉 C++ 编译器不要对其内部的代码进行名称修饰。
-
用法: 如果你有一些 C 函数在 C++ 代码中需要使用(或反之),你可以使用
extern "C"来确保链接时使用正确的函数签名。例如: -
头文件兼容性: 如果你有一个头文件,希望它可以同时被 C 和 C++ 代码所包含,你可以使用以下的结构:
这样,当该头文件被 C++ 代码包含时,extern "C" 将被应用,而当被C代码包含时,它将被忽略。
-
注意事项:
- 使用
extern "C"声明的函数不能被重载。 - C++ 的特性(如类、异常、函数重载等)不能放在
extern "C"块中。
- 使用
-
链接错误: 如果你忘记为C函数添加
extern "C"声明,而试图在 C++ 代码中调用它,你可能会遇到链接错误,因为C++编译器会期望一个名称修饰后的版本的函数,而这个版本在链接时可能不存在。 -
不仅仅是函数:
extern "C"也可以用于全局变量,确保它们在链接时没有名称修饰。
讲一讲四大类型转换表达式¶
在 C++ 中,有四个专门的类型转换表达式,它们分别是:
-
static_cast
这是最常用的转换运算符,用于处理大部分的类型转换场景,但不进行运行时类型检查。它可以进行如下转换:
- 基本数据类型之间的转换,例如
int到float,float到int等。 - 指向父类和子类之间的指针或引用的转换。
- 转换为 void 类型,例如,将任何类型的指针转换为
void*。
例如:
- 基本数据类型之间的转换,例如
-
dynamic_cast
主要用于对象的多态类型转换。当使用该转换运算符进行向下转型(从基类指针或引用转到派生类指针或引用)时,会在运行时检查转换是否合法。这是唯一一个在运行时执行类型检查的转换运算符。
它主要用于以下转换:
- 通过指针或引用从基类转换到派生类。
- 从基类转换回到它的任何派生类。
注意:使用
dynamic_cast进行转换,所涉及的类必须有虚函数,也就是说它们应该是多态的。例如:
-
const_cast
主要用于修改类型的
const或volatile属性。它不能改变类型本身,只能改变类型的这些属性。例如,如果你有一个
const int的指针,你可以使用const_cast来获取一个非const的int指针。 -
reinterpret_cast
这可能是最危险的转换,因为它允许你进行任何指针或引用类型之间的转换,或任何整型到指针(或反向)的转换。这个转换提供了很少的类型安全,并且在使用时应该非常小心。
例如:
这四个类型转换运算符提供了 C++ 中显式类型转换的机制。虽然 C++ 仍然支持旧的 C 风格的类型转换,但使用这些新的转换运算符更加安全和可读
const 和 define 区别?¶
-
定义方式:
#define是一个预处理指令,它在编译前由预处理器处理。const是一个语言关键字,其定义的常量在编译时由编译器处理。
-
类型安全:
#define只是简单地替换文本,不考虑类型。const定义的常量具有明确的类型,这使得const在类型安全性上优于#define。
-
存储:
#define仅仅是一个文本替换工具,并不分配存储空间。const定义的常量通常需要存储空间(例如,对于全局或静态常量),除非编译器可以进行优化。
-
调试:
- 使用
#define定义的宏可能会在调试时带来问题,因为它们在预处理阶段就已经被替换掉了。 const定义的常量更容易在调试中进行识别和跟踪。
- 使用
-
作用域:
#define没有作用域的概念,它的定义一旦出现,会持续到文件结束或者到#undef指令。const变量具有作用域和存储类别,可以是局部的或全局的。
-
其他特性:
#define可以用于定义除常量之外的其他东西,如宏函数。const可以与其他C++特性一起使用,例如类成员、引用等。
const 的作用?¶
-
常量变量:你可以使用
const来定义一个常量,这意味着一旦它被赋值,它的值就不能被改变。 -
常量指针:你可以使用
const定义指针,使得它指向的值或者它本身的地址不能被修改,具体取决于const的位置。- 指针指向常量(指针所指向的内容不能被修改,但指针可以重新指向其他地方):
- 常量指针(指针本身的值,即它所指向的地址不能被修改,但所指向的内容可以被修改):
- 常量指针指向常量(既指针所指向的内容也不能被修改,且指针不能重新指向其他地方):
-
常量引用:通常用于函数参数,使得函数内部不能修改引用的变量。
-
常量成员函数:表示该成员函数不会修改类的任何成员变量。
-
常量表达式:在编译时计算表达式的值,经常与
constexpr关键字一起使用。 -
类对象为常量:如果一个类的对象被声明为
const,那么该对象的任何非常量成员函数都不能在该对象上调用。
指针和引用的区别?¶
引用和指针都是 C++ 中用于访问或修改其他变量的值的机制,但它们之间存在一些关键区别。以下是引用和指针之间的主要区别:
-
定义和初始化:
- 引用 必须在定义时初始化,并且一旦与某个变量绑定,就不能重新绑定到另一个变量。
- 指针 可以在定义时不进行初始化,后续可以任意地修改它所指向的地址。
-
使用方式:
- 当你通过 引用 访问变量时,你直接使用引用的名字,就像使用普通变量一样。
- 使用 指针 时,你需要使用
*运算符来访问它所指向的值。
-
空状态:
- 引用 不能为
nullptr或空状态,它必须总是关联到一个合法的对象。 - 指针 可以是
nullptr,表示它不指向任何对象。
- 引用 不能为
-
修改性:
- 你不能修改 引用 以使其引用另一个对象。
- 你可以随时修改 指针 以使其指向另一个对象或地址。
-
sizeof运算:
sizeof(ref)返回引用所绑定的对象类型的大小。sizeof(ptr)返回指针变量的大小,通常与平台的地址大小有关(例如,在32位系统上为4字节,在64位系统上为8字节)。
-
类型安全:
- 引用在类型方面比指针更加严格。你不能无故地将一个类型的引用赋值给另一个不同的类型,除非涉及继承关系。
- 指针有更多的灵活性,但也带来了更大的风险,例如
void*可以指向任何类型。
-
主要用途:
- 引用 通常用于函数参数和返回类型,使得函数可以直接操作传入的对象,而不是拷贝。
- 指针 有多种用途,包括动态内存分配、数组操作、数据结构(如链表和树)中的链接等。
-
C++11 之后的引用:
https://www.bilibili.com/video/BV1CV4y1h74t/
int (*a)[10] 和 int *a[10] 的区别¶
int (*a)[10] 和 int *a[10] 两者都与指针和数组有关,但它们的含义和用途完全不同。
-
int (*a)[10]:- 这是一个指针,指向一个包含 10 个整数的数组。
a是一个指针,它的类型是 "指向一个整数数组的指针",这个数组有 10 个元素。- 使用这种声明时,你可以让
a指向一个已经存在的大小为 10 的整数数组。 - 例如:
-
int *a[10]:- 这是一个包含 10 个整数指针的数组。
a是一个数组,它的类型是 "包含 10 个整数指针的数组"。- 使用这种声明时,每个数组元素都是一个指针,可以指向一个整数。
- 例如:
为了更好地理解这两者之间的区别,可以考虑如何通过它们访问或修改数据:
- 对于
int (*a)[10],(*a)[i]访问数组的第i个元素。 - 对于
int *a[10],a[i]直接访问第i个指针,而*(a[i])或a[i][0]访问该指针指向的整数。
string/char[]/char* 是不是以 '\0' 结尾¶
在 C 和 C++ 中,字符串通常有以下三种表示:
-
std::string(在 C++ 中):std::string是一个 C++ 标准库类,用于表示和管理字符串。- 它内部管理一个字符数组,但这个数组不一定以 '\0' 结尾。
- 当你使用
std::string::c_str()方法时,它会返回一个以 '\0' 结尾的const char*,这样可以确保与传统的 C 风格字符串兼容。
-
char[](C 风格字符串数组):- 这是一个字符数组,当它被用作字符串时,通常以 '\0' (空字符) 结尾。
- 当你初始化一个字符数组的时候,如
char str[] = "hello";,编译器会自动在末尾加上 '\0' 字符。
-
char*(指向 C 风格字符串的指针):- 这是一个指针,它指向一个字符或字符数组的首地址。当它指向一个字符串时,那个字符串通常以 '\0' 结尾。
- 需要注意的是,只有当这个指针确实指向一个以 '\0' 结尾的字符序列时,它才可以被当作一个完整的字符串。否则,许多处理 C 风格字符串的函数(如
strlen、strcpy等)可能会出现未定义的行为。
总结:char[] 和 char*(当它们用作 C 风格的字符串时)都应该以 '\0' 结尾。而 std::string 不一定以 '\0' 结尾,但当你需要一个以 '\0' 结尾的字符序列时,可以使用 std::string 的 c_str() 方法来获取。
NULL 和 nullptr 的区别?¶
在 C++ 中,NULL 和 nullptr 都可以用于表示空指针,但它们之间存在一些关键差异:
-
历史与兼容性:
NULL:它的使用在 C 和 C++ 中都有很长的历史。在 C++ 中,NULL通常被定义为整数 0 或((void*)0)(取决于实现)。nullptr:这是 C++11 中引入的新关键字,专门用于表示空指针。
-
类型:
NULL:在 C++ 中,NULL实际上是整数类型(通常是int)。这有时会导致某些意料之外的函数重载行为。nullptr:它具有自己的类型——std::nullptr_t。这种类型只有一个可能的值,即nullptr本身,可以隐式地转换为所有指针类型,但不能隐式地转换为整数类型。
-
安全性:
- 由于
NULL是整数类型,使用它可能导致某些意料之外的函数重载解析或隐式转换,这可能不安全。 nullptr提供了更强的类型安全性。例如,考虑以下重载: 如果你使用func(NULL);,则会调用func(int val);而不是你可能期望的func(char* ptr);。但如果你使用func(nullptr);,则会正确地调用func(char* ptr);。
- 由于
C++程序的编译过程?¶
-
预处理(Preprocessing):
- 在这一步,预处理器处理所有以
#开头的预处理指令,如#include、#define、#ifdef等。 #include会将相关的头文件内容直接包含进源文件。- 宏会被展开,条件编译指令会根据条件选择性地包括或排除代码部分。
- 这个步骤不会生成新的文件,除非你显式地请求编译器保存预处理后的文件。例如,使用 GCC 编译器时,你可以用
-E选项来仅执行预处理并输出结果。
输入文件:
source.cpp输出文件(假设你选择保存预处理结果):
source.iorsource.ii - 在这一步,预处理器处理所有以
-
编译(Compilation):
- 在这一步,编译器将预处理后的代码转换为汇编语言。
- 这是将高级语言转换为更低级语言的过程,但还没有到机器代码的级别。
输入文件:
source.iorsource.ii(或直接是源文件)输出文件:
source.s或source.asm(取决于编译器和平台) -
汇编(Assembly):
- 汇编器将汇编语言代码转换为机器语言代码,结果是一个对象文件。
- 对象文件包含了你的代码转换为的机器指令,但它还不是一个完整的程序,因为它可能还依赖于其他对象文件或库。
输入文件:
source.s或source.asm输出文件:
source.o或source.obj(取决于编译器和平台) -
链接(Linking):
- 在这一步,链接器取多个对象文件和库,将它们链接在一起,生成一个可执行文件或库文件。
- 如果程序中使用了外部库或分多个源文件编写,那么链接器将确保所有的函数调用都正确地指向了其定义的地方。
- 链接器还处理静态和动态库的依赖关系。
输入文件:
source.o(或其他对象文件和库)输出文件:通常是
a.out(在某些 UNIX 系统上) 或source.exe(在 Windows 上)或其他可执行文件名
这就是 C++ 程序的标准编译过程。不同的编译器和平台可能会有细微的差异,但基本概念和步骤都是相似的。
动态链接和静态链接的区别?¶
-
定义:
- 静态链接:在链接阶段,所有程序所需的库函数都被复制并集成到最终的可执行文件中。这意味着可执行文件是独立的,不依赖于外部的库文件。
- 动态链接:可执行文件不包含实际的库函数,而是包含对动态链接库(如
.dll在 Windows 或.so在 UNIX-like 系统上)的引用。当程序运行时,这些库被加载到内存中并与程序链接。
-
文件大小:
- 静态链接:由于所有必要的库都被链接到可执行文件中,所以这些文件往往更大。
- 动态链接:可执行文件通常较小,因为它只包含对库的引用而不是库的实际代码。
-
独立性:
- 静态链接:生成的可执行文件是独立的,不依赖于外部库。这使得分发简单,因为用户只需要一个文件。
- 动态链接:如果动态链接库在目标系统上缺失或版本不匹配,程序可能无法运行。
-
运行时开销:
- 静态链接:运行时没有加载外部库的开销。
- 动态链接:运行程序时,必须加载和链接动态链接库,这可能会增加一些开销。但多个程序可以共享同一动态库的单一内存实例。
讲一讲 C++ 符号表¶
https://zhuanlan.zhihu.com/p/600009670