当前位置: 首页 > news >正文

c++面试常问1

常量指针和指针常量的区别是什么?

常量指针:常量指针本质上是个指针,只不过这个指针指向的对象是常量。也就是说,不可以通过对指针解引用修改指针指向的内容,而可以修改指针的指向。

指针常量的本质上是个常量,只不过这个常量的值是一个指针,和上面那个恰恰相反,不可以修改指针的指向,但可以通过对指针解引用修改指针指向的内容。

特性常量指针 (const int *p)指针常量 (int *const p)
const位置*左边*右边
修饰对象修饰指针指向的值修饰指针本身
能否修改指向的值不能 (*p = ...非法)能 (*p = ...合法)
能否修改指针指向能 (p = &...合法)不能 (p = &...非法)
初始化要求不强制在定义时初始化必须在定义时初始化

函数指针和指针函数的区别是什么?

指针函数本质是一个函数,只不过该函数的返回值是一个指针。相对于普通函数而言,只是返回值是指针。

#include <stdlib.h> // 一个返回 int* 的指针函数 int* create_array(int size) { // 动态分配内存 int *arr = (int*)malloc(size * sizeof(int)); return arr; // 返回堆内存的地址 } int *my_array = create_array(10); // 调用函数,获取返回的地址 // ... 使用 my_array ... // 注意:使用完毕后需要手动释放内存 free(my_array);

函数指针,顾名思义,是“指向函数的指针”。它的核心特点是:它是一个指针变量,存储了某个函数的入口地址,通过这个指针可以间接调用该函数

  • 语法形式*和指针名被括号()包裹。
    • int (*p)(int, int);
int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int (*func_ptr)(int, int); // 声明一个函数指针 func_ptr = add; // 指向 add 函数 int result1 = func_ptr(5, 3); // 调用 add,result1 = 8 func_ptr = sub; // 改变指向,指向 sub 函数 int result2 = (*func_ptr)(5, 3); // 调用 sub,result2 = 2
特性函数指针 (Pointer to Function)指针函数 (Function Returning Pointer)
本质指针变量函数
语法关键(*指针名)返回类型* 函数名
作用存储函数地址,实现间接调用执行逻辑,并返回一个内存地址
典型用途回调函数、策略模式动态内存分配、返回数组/结构体地址

指针和引用值传递的区别是什么?

本质区别:别名 vs. 地址副本

特性引用传递 (T&)指针传递 (T*)
语法操作直接使用,像普通变量一样 (x = 10)需要解引用 (*x = 10)
空值处理不能为空 (必须绑定有效对象)可以为空 (nullptr)
可变性一旦初始化,不可重绑定到其他变量指针可以重新赋值指向其他地址
安全性高 (无空指针风险,无需检查)较低 (需防范空指针、野指针)
可读性高 (代码简洁,意图明确)较低 (需显式取地址和解引用)

引用 vs. 指针:为什么引用更受欢迎?

虽然指针也能实现类似的功能,但引用在大多数情况下是更优的选择,因为它更安全、更清晰。

特性引用传递 (T&)指针传递 (T*)
安全性高。引用不能为空,必须在初始化时绑定到一个有效对象,避免了空指针解引用的风险source_group_web_11。较低。指针可以为nullptr,使用前必须进行判空检查,否则可能导致程序崩溃source_group_web_12。
可读性高。调用时直接传递变量,函数内部直接操作,语法简洁自然source_group_web_13。较低。调用时需要取地址符&,函数内部需要解引用符*->,代码稍显繁琐source_group_web_14。
可变性不可重绑定。引用一旦绑定到某个变量,就不能再指向其他变量source_group_web_15。可重定向。指针可以随时被赋予新的地址,指向不同的对象source_group_web_16。

参数传递时,值传递、引用传递、指针传递的区别?

简单来说:

  • 值传递:创建副本,安全但可能低效。
  • 引用传递:创建别名,高效且安全。
  • 指针传递:传递地址副本,高效但需谨慎。
特性值传递 (Pass by Value)引用传递 (Pass by Reference)指针传递 (Pass by Pointer)
本质创建实参的完整副本实参的别名传递实参地址的副本
修改影响不影响原始实参直接影响原始实参解引用后直接影响原始实参
空值处理不适用(总是有效对象)不能为空 (必须绑定有效对象)可以为空 (nullptr)
性能开销高(尤其对大型对象)极低(无拷贝)低(仅拷贝地址)
语法void func(Type param)void func(Type& param)void func(Type* param)

C++全局变量、局部变量、静态全局变量、静态局部变量的区别?

C++ 变量根据定义的位置的不同的生命周期,具有不同的作用域,作用域可分为 6 种:全局作用域,局部作用域,语句作用域,类作用域,命名空间作用域和文件作用域。

全局变量:具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。

静态全局变量:具有文件作用域。它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static 关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。

局部变量:具有局部作用域。它是自动对象(auto),在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。

静态局部变量:具有局部作用域。它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。

简单来说:

  • 局部变量:临时工,用完即走。
  • 全局变量:公共财产,全程序共享。
  • 静态局部变量:有记忆的临时工,只在函数内可见。
  • 静态全局变量:私有财产,只在当前文件内共享。
变量类型作用域 (在哪能用)生命周期 (活多久)链接性 (能否跨文件)存储位置初始化行为
局部变量函数或代码块内部函数执行期间无链接栈区不初始化则为随机值
全局变量定义点之后的所有文件整个程序运行期外部链接静态存储区自动初始化为0
静态全局变量定义点之后的当前文件整个程序运行期内部链接静态存储区自动初始化为0
静态局部变量函数或代码块内部整个程序运行期无链接静态存储区只初始化一次,默认为0

6 种作用域分类

C++ 变量根据定义的位置的不同的生命周期,具有不同的作用域,作用域可分为 6 种:全局作用域,局部作用域,语句作用域,类作用域,命名空间作用域和文件作用域。

1. 文件作用域 (File Scope)

这通常就是指全局作用域。在所有函数和类之外定义的变量就属于这个作用域。

  • 可见性:从定义点开始,直到当前源文件(.cpp)的末尾。
  • 生命周期:贯穿整个程序的运行期。
  • 链接性:默认具有外部链接,意味着其他文件可以通过extern关键字来访问它。

2. 局部作用域 (Local Scope)

这是在函数内部定义的变量,也就是我们常说的局部变量

  • 可见性:仅在定义它的函数或代码块{}内部有效。
  • 生命周期:当函数被调用时创建,函数返回时销毁。
  • 存储位置:通常在栈(Stack)上。

3. 语句作用域 (Statement Scope)

这通常被称为块作用域(Block Scope),是局部作用域的一种更精细的形式。它指的是在任何一个代码块(由{}包围)内部定义的变量。

  • 可见性:仅在它所在的那个特定代码块内有效。例如,for循环或if语句中定义的变量。
  • 生命周期:程序执行进入该块时创建,离开该块时销毁。
  • 典型场景for(int i = 0; ...)中的循环变量i

4. 类作用域 (Class Scope)

这是在类(classstruct)内部定义的变量,即类的成员变量

  • 可见性:在类的所有成员函数内部有效。外部需要通过对象(obj.member)或指针(ptr->member)来访问。
  • 生命周期:与类的实例(对象)的生命周期相同。对象创建时成员变量诞生,对象销毁时成员变量消亡。

5. 命名空间作用域 (Namespace Scope)

这是在命名空间(namespace)内部定义的变量。

  • 可见性:在命名空间内部有效。外部需要通过作用域解析运算符::来访问,例如std::cout
  • 生命周期:如果命名空间内定义的是全局变量,则生命周期贯穿整个程序运行期。
变量类型典型作用域生命周期链接性
局部变量局部/块作用域自动(函数/块结束)无链接
静态局部变量局部/块作用域静态(程序结束)无链接
全局变量文件/全局作用域静态(程序结束)外部链接
静态全局变量文件作用域静态(程序结束)内部链接
类成员变量类作用域与对象共存亡无链接

全局变量定义在头文件中有什么问题?

如果在头文件中定义全局变量,当该头文件被多个文件 include 时,该头文件中的全局变量就会被定义多次,导致重复定义,因此不能再头文件中定义全局变量。

extern C 的作用是什么?

extern "C"的核心作用非常明确:实现 C++ 代码与 C 代码的混合编程

简单来说,它是一条编译指令,告诉 C++ 编译器:“请按照 C 语言的规则来处理这部分代码(主要是函数名和变量名)”。

当 C++ 程序 需要调用 C 语言编写的函数,C++ 使用链接指示,即 extern “C” 指出任意非 C++ 函数所用的语言。 举例:

// 可能出现在 C++ 头文件<cstring>中的链接指示 extern "C"{ int strcmp(const char*, const char*); }

C 和 C++ struct 的区别?

struct只是一个纯粹的数据容器,而 C++ 的struct是一个功能完备的面向对象类型。

  • 在 C 语言中 struct 是用户自定义数据类型;在 C++ 中 struct 是抽象数据类型,支持成员函数的定义。
  • C 语言中 struct 没有访问权限的设置,是一些变量的集合体,不能定义成员函数;C++ 中 struct 可以和类一样,有访问权限,并可以定义成员函数。
  • C 语言中 struct 定义的自定义数据类型,在定义该类型的变量时,需要加上 struct 关键字,例如:struct A var;定义 A 类型的变量;而 C++ 中,不用加该关键字,例如:A var;
特性C 语言中的structC++ 中的struct
成员函数不支持支持(包括构造函数、析构函数)
访问控制无(所有成员默认且只能是public支持(public/private/protected
继承不支持支持(可继承其他structclass
变量声明必须带struct关键字可以省略struct关键字
初始化仅支持聚合初始化支持聚合初始化、构造函数、C++11 成员初始化
默认继承权限不适用public
class的关系class概念几乎等同于class,仅默认权限不同

C++ 中 struct和Class区别是什么?

简单来说:struct更偏向于作为纯粹的数据容器,而class更偏向于作为具有封装和行为的对象。

默认访问权限不同

  • struct: 成员默认是public的。
  • class: 成员默认是private的。
struct MyStruct { int x; // 默认为 public,外部可以直接访问 }; class MyClass { int x; // 默认为 private,外部无法直接访问 };

默认继承方式不同

  • struct: 继承时默认是public继承。
  • class: 继承时默认是private继承。
struct Base {}; struct DerivedStruct : Base {}; // 默认为 public 继承 class DerivedClass : Base {}; // 默认为 private 继承

为什么有了 class 还保留 struct?

这主要是为了向后兼容语义表达。虽然 C++ 把struct升级得几乎和class一样强大,但保留它绝对不是为了制造重复,而是因为它在表达“纯数据结构”时更直观。

  • struct暗示“纯数据”:当你看到一个struct时,你会本能地认为这是一个简单的数据集合(Data Carrier),它是公开的、透明的,没有复杂的逻辑封装。比如坐标点Point、矩形尺寸Rect、数据库的一行记录等。
  • class暗示“对象”:当你看到一个class时,你会认为这是一个封装了数据和行为的对象,它有私有成员,有复杂的内部逻辑,对外提供接口。

保留struct让代码的意图更清晰。如果你写了一个只有公有成员变量的类型,用struct比用class更符合直觉。

struct 和 union 的区别是什么?

核心区别:内存分配方式。这是两者最本质的区别。

  • struct(结构体):每个成员都有自己独立的内存空间。

    • 结构体的总大小 = 所有成员大小之和 +内存对齐填充的字节。
    • 修改一个成员,不会影响其他成员。
    • 所有成员可以同时有效。
  • union(共用体/联合体):所有成员共享同一块内存空间。

    • 共用体的总大小 =最大的那个成员的大小(同样需要考虑对齐)。
    • 修改一个成员,会覆盖其他成员的数据(因为大家用的是同一块地)。
    • 在同一时刻,只能有一个成员有效。
特性struct(结构体)union(共用体)
内存模式每个成员独立分配内存所有成员共享首地址,重叠存放
总大小所有成员大小之和 (含对齐)= 最大成员的大小 (含对齐)
数据存取各成员互不干扰,可同时使用写入一个会覆盖另一个,一次只能用其一
主要用途组合相关的数据(如:学生信息)节省内存、数据类型转换、寄存器操作

struct 示例

struct MyStruct { int a; // 4字节 char b; // 1字节 }; // 总大小通常是 8字节 (因为内存对齐) MyStruct s; s.a = 65; // 内存前4字节存入 65 s.b = 'B'; // 内存第5字节存入 'B' // s.a 依然是 65,s.b 是 'B',互不影响。
union MyUnion { int a; // 4字节 char b; // 1字节 }; // 总大小是 4字节 (取决于最大的 int) MyUnion u; u.a = 65; // 4字节内存被写入 65 (十六进制 0x00000041) u.b = 'B'; // 第1字节被覆盖为 'B' (ASCII 66, 0x42) // 此时内存变为 0x00000042 // 结果: // u.b 是 'B' (66) // u.a 变成了 66 (不再是 65!因为低字节被 b 覆盖了)

C 和 C++ static 的区别是什么?

特性C 语言中的staticC++ 中的static
核心用途控制变量/函数的作用域和生命周期包含 C 的所有用途,并增加类成员的归属与共享
面向对象无关紧密相关,是类概念的核心部分
数据共享在同一函数或同一文件内共享可在同一类的所有对象间共享数据
函数调用静态函数只能在本文件内调用静态成员函数可通过类名直接调用,无需对象
this指针不适用静态成员函数没有this指针

C++ static作用是什么?

在 C++ 中,static关键字的作用非常丰富,它的核心语义根据使用场景的不同而变化,但总体上可以归纳为两个核心目的:控制生命周期控制作用域/可见性

1.修饰函数内的局部变量:延长生命周期

static用于函数内部的局部变量时,它改变了变量的存储位置和生命周期。

  • 作用:变量不再存储在栈上,而是存储在静态存储区。它的生命周期从“函数调用结束即销毁”延长至“整个程序运行结束”。
  • 效果
    1. 只初始化一次:该变量在程序第一次执行到其定义语句时进行初始化,后续的函数调用会跳过初始化步骤。
    2. 值被保留:函数调用结束后,变量的值不会丢失,下次调用时会保留上次调用结束时的值。
  • 典型应用:统计函数调用次数、实现懒加载(Lazy Initialization)等。

2. 修饰全局变量或普通函数:限制作用域(文件作用域)

static用于函数外部(全局作用域)的变量或函数时,它改变了符号的链接性(linkage)。

  • 作用:将变量或函数的链接性从“外部链接”(可被其他源文件访问)变为“内部链接”。
  • 效果:该变量或函数的作用域被限制在定义它的源文件(.cpp文件)内部,对其他文件不可见。
  • 典型应用:避免不同源文件间的命名冲突,实现“文件私有”的全局变量或辅助函数。
// file1.cpp static int globalVal = 10; // 仅在 file1.cpp 中可见 static void helperFunc() { /* ... */ } // 仅在 file1.cpp 中可见 // file2.cpp static int globalVal = 20; // 与 file1.cpp 中的 globalVal 是两个不同的变量,不会冲突

3. 修饰类的成员变量:实现类级别的数据共享

这是 C++ 面向对象特性中static的重要应用。

  • 作用:声明一个属于类本身,而非类的某个具体对象的变量。
  • 效果
    1. 所有对象共享:该类的所有实例共享同一份静态成员变量。
    2. 不占用对象内存:它不计算在sizeof(对象)的大小内。
    3. 访问方式:可以通过类名::变量名直接访问,无需创建对象。
  • 初始化:静态成员变量必须在类外进行定义和初始化(除非是static const的整型或 C++17 的inline static)。

4. 修饰类的成员函数:实现类级别的工具方法

与静态成员变量类似,静态成员函数也属于类本身。

  • 作用:声明一个属于类的函数。
  • 效果
    1. this指针:静态成员函数不与任何对象绑定,因此没有this指针。
    2. 访问限制:它只能访问类的静态成员变量和其他静态成员函数,不能直接访问非静态成员。
    3. 访问方式:可以通过类名::函数名()直接调用,无需创建对象。
  • 典型应用:工厂方法、工具函数、访问静态数据的接口等。
使用位置核心作用效果
函数内部延长生命周期变量“记住”上次的值,只初始化一次
文件作用域限制作用域变量/函数“文件私有”,避免命名冲突
类内部 (变量)实现数据共享所有对象共享一份数据,属于类本身
类内部 (函数)实现类级方法无需对象即可调用,没有this指针

static 在类中使用的注意事项有哪些?

static 静态成员变量:

  • 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static关键字和private、public、protected 访问规则。
  • 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。
  • 静态成员变量可以作为成员函数的参数,而普通成员变量不可以。

static 静态成员函数:

  • 静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。静态成员函数做为类作用域的全局函数。
  • 静态成员函数不能声明成虚函数(virtual)、const 函数和 volatile 函数。

static 全局变量和普通全局变量的异同是什么?

特性普通全局变量Static 全局变量
作用域全局(跨文件可见)文件内(仅当前文件可见)
链接属性外部链接 (External Linkage)内部链接 (Internal Linkage)
外部访问可以通过extern访问无法被其他文件访问
存储位置静态存储区 (.data/.bss)静态存储区 (.data/.bss)
生命周期程序运行全程程序运行全程
默认初始化00
主要用途跨模块共享数据模块内部私有状态,避免冲突
// file1.c int globalVar = 10; // 普通全局变量 static int staticVar = 20; // static全局变量 void func1() { globalVar++; // 可以访问 staticVar++; // 可以访问 } // file2.c extern int globalVar; // 正确:声明外部变量,链接到 file1.c 的 globalVar // extern int staticVar; // 错误!链接器找不到 staticVar,因为它被限制在 file1.c 中 void func2() { globalVar = 100; // 正确:修改了 file1.c 中的变量 // staticVar = 200; // 错误:编译或链接错误,不可见 }

C++ 静态变量的使用场景是什么?未初始化的全局静态变量呢?

静态变量(包括全局静态、局部静态、类静态成员)的核心特点是生命周期贯穿程序运行始终,且作用域受限定,常见使用场景如下:

全局静态变量(static修饰的全局变量)

  • 作用:限制变量仅在当前文件内可见(避免不同文件中同名变量冲突),但生命周期是整个程序运行期间。
  • 场景:当多个文件需要独立使用同名变量(如统计各模块的内部计数),但不希望被其他文件访问或修改时。例:static int count = 0;(仅当前.cpp文件可访问,其他文件即使声明extern int count也无法使用)。

局部静态变量(函数内的static变量)

  • 作用:变量在函数第一次调用时初始化,后续调用不再重新初始化,值会被保留(生命周期全局,作用域仅限函数内)。
  • 场景:记录函数被调用的次数(如static int call_count = 0; call_count++;);单例模式中,确保全局只存在一个实例(如函数内返回静态对象的指针);避免频繁创建销毁临时对象(如工具函数中复用的缓冲区)。

类静态成员变量(static修饰的类成员)

  • 作用:属于整个类而非某个对象,所有对象共享该变量,生命周期全局,需在类外单独初始化。
  • 场景:统计类的实例数量(如static int total;,在构造函数中total++,析构函数中total--);存储类级别的常量或共享配置(如static const int MAX_SIZE = 100;)。

未初始化的全局静态变量

未初始化的全局静态变量(如static int a;)有两个关键特性:

  1. 自动初始化:编译器会将其默认初始化为0(包括数值类型为0,指针类型为nullptr等)。这是因为全局静态变量存放在内存的BSS 段(未初始化数据段),程序启动时系统会自动将该段所有数据清零。
  2. 作用域限制:和初始化的全局静态变量一样,仅在当前文件内可见,不影响其他文件的同名变量。
// file1.cpp static int uninit; // 未初始化,默认值为0,仅file1可见 // file2.cpp static int uninit; // 与file1的uninit无关,各自为0

介绍const 作用及用法?

作用:

  • const修饰成员变量,定义成const常量,相较于宏常量,可进行类型检查,节省内存空间,提高了效率。
  • const修饰函数参数,使得传递过来的函数参数的值不能改变。
  • const修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable修饰的变量除外),也不能调用非const成员函数,因为非const成员函数可能会修改成员变量。

在类中的用法:

const 成员变量:

  • const成员变量只能在类内声明、定义,在构造函数初始化列表中初始化。
  • const成员变量只在某个对象的生存周期内是常量,对于整个类而言却是可变的,因为类可以创建多个对象,不同类的const成员变量的值是不同的。因此不能在类的声明中初始化const成员变量,类的对象还没有创建,编译器不知道他的值。

const 成员函数:

  • 不能修改成员变量的值,除非有mutable修饰;只能访问成员变量。
  • 不能调用非常量成员函数,以防修改成员变量的值。

修饰变量:定义常量:这是const最基本的用法,用于声明一个在初始化后其值不能被改变的变量。

  • 作用:防止变量被意外修改,编译器会在编译期进行检查,任何修改const变量的尝试都会导致编译错误。
  • 特点
    1. 必须初始化const变量在定义时必须被赋予一个初始值。
    2. 类型安全const变量有明确的数据类型,编译器会进行类型检查。
    3. 内部链接:在 C++ 中,const全局变量默认具有内部链接属性,这意味着它们只在定义它们的源文件(.cpp)内可见,避免了不同文件间的命名冲突。
const double PI = 3.14159; // PI = 3.14; // 编译错误:不能修改常量

修饰指针和引用

const与指针和引用结合使用时,可以提供更细粒度的控制。一个简单有效的记忆方法是:const*左边,修饰的是指针指向的内容;const*右边,修饰的是指针本身。

指向常量的指针:指针指向的内容是常量,不能通过该指针来修改内容,但指针本身可以指向其他地址。

int a = 10, b = 20; const int* ptr = &a; // 或者 int const* ptr = &a; // *ptr = 30; // 编译错误:不能通过 ptr 修改 a 的值 ptr = &b; // 正确:ptr 可以指向别的地址

常量指针:指针本身是常量,一旦初始化就不能再指向其他地址,但可以通过该指针修改它所指向的内容。

int a = 10, b = 20; int* const ptr = &a; *ptr = 30; // 正确:可以修改 a 的值 // ptr = &b; // 编译错误:ptr 不能指向别的地址

指向常量的常量指针:指针本身和它指向的内容都是常量,两者都不能被修改。

int a = 10; const int* const ptr = &a; // *ptr = 30; // 编译错误 // ptr = &b; // 编译错误

常量引用:引用本身在创建时就必须绑定到一个对象,因此它天生就是“常量指针”。const引用 的主要用途是避免拷贝大对象,同时保证函数不会修改传入的参数。

void printValue(const std::string& str) { // str += "hello"; // 编译错误:不能修改 str std::cout << str << std::endl; }

修饰函数参数和返回值

1.修饰函数参数:通常与引用或指针结合使用,目的是防止函数内部修改传入的参数,同时避免不必要的拷贝,提高效率。

// 保证函数不会修改传入的 data 对象 void processData(const std::vector<int>& data);

2. 修饰函数返回值:当函数返回一个指针或引用时,使用const可以防止调用者通过返回值修改函数内部的私有数据。

class DataHolder { int secret_data; public: // 返回一个常量引用,调用者不能修改 secret_data const int& getSecret() const { return secret_data; } };

修饰类成员函数

这是const在面向对象编程中的关键应用。将const放在成员函数的参数列表之后,表示这是一个“常成员函数”。

  • 作用:承诺该函数不会修改调用它的对象的任何非静态成员变量。
  • 重要性const对象只能调用const成员函数。这是一个重要的设计原则,它清晰地区分了“读取操作”(const函数)和“修改操作”(非const函数)。
class Point { private: int x, y; public: Point(int x_val, int y_val) : x(x_val), y(y_val) {} // 常成员函数:只读取数据,不修改 int getX() const { return x; } // 非常量成员函数:会修改数据 void setX(int new_x) { x = new_x; } }; int main() { const Point p(1, 2); // 定义一个常量对象 p.getX(); // 正确:const 对象可以调用 const 成员函数 // p.setX(10); // 编译错误:const 对象不能调用非 const 成员函数 return 0; }

define 和 const 的区别是什么?

  • #define(预处理指令)

    • 阶段:在预处理阶段进行简单的文本替换(宏替换)。
    • 机制:编译器在编译代码之前,会将代码中所有的宏名直接替换为定义的值。它没有类型概念,也不分配内存(除非替换后的代码导致分配)。
    • 例子#define PI 3.14,编译器看到的是3.14,不知道PI是什么。
  • const(关键字)

    • 阶段:在编译和运行阶段起作用。
    • 机制:它定义了一个具有明确类型的变量,由编译器进行管理。它有数据类型,占用内存(通常在只读数据段或符号表中),并且受作用域规则限制。
    • 例子const double PI = 3.14;,编译器知道PI是一个double类型的常量。
特性#defineconst
类型检查无。仅仅是文本替换,不进行类型检查,容易引发隐患。有。编译器会严格检查类型,类型不匹配会报错。
内存分配不分配(通常)。只是替换文本,不占用内存空间(除非多次使用导致多次产生立即数)。分配。作为变量存储在内存中(通常是只读段),调试时可以查看其地址。
作用域无作用域限制。一旦定义,直到被#undef取消或文件结束都有效。有作用域。遵循块作用域、命名空间作用域等规则(如局部const、类内const)。
调试困难。预处理后宏名消失,调试器看不到宏名,只能看到具体的值。容易。调试器可以识别const变量名,方便调试。
安全性低。容易发生“边际效应”错误(见下文例子)。高。避免了宏替换带来的逻辑错误。

define 和 typedef 的区别是什么?

本质的区别:#define是预处理阶段的文本替换,而typedef是编译阶段对类型进行真正的别名定义。

处理阶段不同

  • #define

    • 预处理阶段进行处理。编译器在正式编译之前,预处理器会将代码中所有的宏名简单地替换为定义的文本。
    • 它不理解 C++ 的语法,只是纯粹的字符串操作。
  • typedef

    • 编译阶段进行处理。由编译器解析,它理解 C++ 的语法和类型系统。
    • 它为现有的类型创建一个新的别名,这个别名会被编译器记录在符号表中。

🧩 功能与原理不同

  • #define

    • 文本替换。它不仅可以定义类型别名,还可以定义常量、宏函数等。
    • 例如:#define INT int,在预处理后,代码中所有的INT都会变成int
  • typedef

    • 类型别名。它专门用于给类型起别名,不能用于定义常量。
    • 例如:typedef int INT;,编译器知道INTint类型的别名。

指针声明时的陷阱(关键区别)

这是两者最显著的区别,特别是在处理指针时。

  • #define:由于是简单的文本替换,容易导致意想不到的结果。

#define PTR_INT int* PTR_INT a, b; // 预处理后变成:int* a, b; // 结果:a 是 int* 类型(指针),但 b 是 int 类型(整数)!

typedef:遵循 C++ 的类型声明规则,行为符合直觉。

typedef int* PTR_INT; PTR_INT a, b; // 编译器理解为:a 和 b 都是 PTR_INT 类型(即 int*) // 结果:a 和 b 都是 int* 类型(指针)。

作用域不同

  • #define无作用域限制。一旦定义,直到文件结束或被#undef取消,它在任何地方都有效。这可能导致命名冲突。

  • typedef有作用域。遵循块作用域、命名空间作用域等规则。例如,在函数内定义的typedef只在该函数内有效。

🛡️ 类型检查

  • #define无类型检查。因为它只是文本替换,编译器在编译时看不到宏名,无法进行类型安全检查。

  • typedef有类型检查。编译器知道别名的真实类型,可以进行严格的类型检查。

特性#definetypedef
处理阶段预处理阶段编译阶段
本质文本替换类型别名
指针声明容易出错(#define PTR int*安全正确(typedef int* PTR
作用域全局(无作用域)有作用域(块、命名空间等)
类型检查
功能定义常量、宏函数、类型别名等仅定义类型别名
#include <iostream> #define INTPTR1 int * typedef int * INTPTR2; using namespace std; int main() { INTPTR1 p1, p2; // p1: int *; p2: int INTPTR2 p3, p4; // p3: int *; p4: int * int var = 1; // 相当于 const int * p5; 常量指针,即不可以通过 p5 去修改 p5 指向的内容 const INTPTR1 p5 = &var; // 相当于 int * const p6; 指针常量,不可使 p6 再指向其他内容 const INTPTR2 p6 = &var; return 0; }

volatile 的作用?是否具有原子性,对编译器有什么影响?

volatile 的作用:当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 volatile,告知编译器不应对这样的对象进行优化。

volatile不具有原子性。

volatile 对编译器的影响:使用该关键字后,编译器不会对相应的对象进行优化,即不会将变量从内存缓存到寄存器中,防止多个线程有可能使用内存中的变量,有可能使用寄存器中的变量,从而导致程序错误。

什么情况下一定要用 volatile, 能否和 const 一起使用?

使用 volatile 关键字的场景:

  • 当多个线程都会用到某一变量,并且该变量的值有可能发生改变时,需要用 volatile 关键字对该变量进行修饰;
  • 中断服务程序中访问的变量或并行设备的硬件寄存器的变量,最好用 volatile 关键字修饰。

volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const,同时具有二者的属性。

http://www.cnnetsun.cn/news/2131386.html

相关文章:

  • 避坑指南:在Ubuntu 20.04上为OpenHarmony 3.x编译环境配置Python和pip(实测有效)
  • GetQzonehistory:免费开源QQ空间说说备份工具终极指南
  • 3步解决音乐歌词获取难题:163MusicLyrics歌词提取工具实战指南
  • Ghost Pepper 极致辣度与风味实测报告
  • 避开STC8H串口调试的那些坑:从波特率计算到引脚配置的保姆级排错指南
  • 车队管理系统:车辆调度与路线优化的算法
  • Redis主从复制实战和哨兵服务
  • Real-Anime-Z模型底层交互:使用C语言进行高性能扩展开发
  • Node.js在前端开发中扮演的角色
  • Halcon 3D视觉入门:用gen_plane_object_model_3d()创建‘虚拟基准面’,搞定工件定位与平面度检测
  • 如何用普通摄像头实现瞳孔追踪:eyeLike开源项目完全指南
  • League Akari:英雄联盟玩家的终极工具箱完整使用指南
  • 从SMR硬盘到ZNS SSD:聊聊‘叠瓦式’存储思想的跨界与新生
  • 安卓虚拟摄像头终极指南:用VCAM实现视频替换的完整方案
  • MinerU:OpenDataLab数据集的智能下载与自动化管理工具
  • 如何突破网盘限速:终极网盘下载加速工具使用指南
  • RoundedTB:从新手到专家的Windows任务栏美化完整指南
  • 如何通过STM32F103平台构建高性能工业级CNC控制系统?
  • 人工智能术语查询太头疼?这个开源项目让你3分钟搞定专业翻译!
  • CHIP LAN(片式网络变压器)选型实用指南
  • 3步智能配置黑苹果:OpCore-Simplify零基础EFI生成解决方案
  • 快速免费清理Windows 11系统臃肿的终极解决方案:Win11Debloat使用完全指南
  • 为什么你的C++控制模块通不过ISO 26262 ASIL-B评审?(2024最新SGS审核清单+12处隐性非符合项逐行标注)
  • GPEN修复效果对比实测:科哥版处理前后,细节提升肉眼可见
  • UTM虚拟机:3分钟在iOS和macOS上运行Windows和Linux的完整指南
  • STM32F103C8T6驱动MAX30102心率血氧传感器,从硬件接线到算法调试的完整避坑指南
  • 从vfork到写时复制:深入Linux进程创建的底层机制与性能选择
  • 每日热门skill:93% Token节省!Vercel开源的AI浏览器神器,让Claude Code秒变网页操作专家
  • HTTPS 证书配置完全指南:从申请到自动化续期
  • Windows系统终极光盘模拟方案:WinCDEmu完整使用指南