1.C和C++的区别
特性 | C | C++ |
---|---|---|
编程范式 | 面向过程编程 | 面向对象编程 + 面向过程编程 + 泛型编程 |
类和对象 | 不支持类和对象 | 支持类和对象,封装、继承、多态等特性 |
标准库 | 标准库有限,如 stdio.h 、stdlib.h | 丰富的标准库,如 STL(容器、算法) |
函数和运算符重载 | 不支持 | 支持 |
内存管理 | 手动管理,使用 malloc 和 free | 提供 new 和 delete ,支持智能指针 |
类型检查 | 类型检查较弱 | 类型检查更严格,支持多种类型转换 |
异常处理 | 不支持 | 支持异常处理机制(try-catch ) |
命名空间 | 不支持 | 支持命名空间(namespace ) |
内联函数 | 通过宏定义(#define )实现 | 支持 inline 函数,编译器优化 |
多范式支持 | 单一的面向过程编程 | 多范式支持:面向对象、泛型、函数式 |
模板 | 不支持 | 支持模板,泛型编程 |
性能 | 运行时性能高,但功能相对有限 | 功能强大,优化后性能可与 C 相当 |
C 是一种面向过程的编程语言,强调通过函数和过程来组织代码,具有较高的执行效率和低级别控制能力。它不支持类和对象,也没有面向对象编程的特性。此外,C 的标准库较为有限,内存管理需要通过 malloc
和 free
手动进行,类型检查较弱,且没有异常处理和命名空间的支持。由于其简洁性和高性能,C 主要用于系统编程和嵌入式开发。
C++ 则是 C 的扩展,支持面向对象编程,并且引入了类、继承、多态等高级特性,允许通过对象来组织代码。C++ 拥有强大的标准库,特别是标准模板库(STL),可以简化数据结构和算法的实现。它还支持函数和运算符重载,内存管理更加灵活,支持 new
和 delete
,并通过智能指针减少内存泄漏。此外,C++ 提供了严格的类型检查、异常处理、模板(泛型编程)和命名空间功能,使得它适合构建复杂的、高度模块化的应用程序。
总的来说,C 更适合低级别的系统编程,而 C++ 则因其丰富的特性和库支持,更适合开发大型应用程序,尤其是在需要面向对象和泛型编程的场景下。
2. C语言的结构体和C++的有什么区别?
C 语言和 C++ 中的结构体(struct
)有许多相似之处,但 C++ 扩展了结构体的功能,使其比 C 语言中的结构体更强大。以下是两者之间的主要区别:
特性 | C 语言的结构体 | C++ 中的结构体 |
---|---|---|
数据成员 | 只能包含数据成员 | 可以包含数据成员和成员函数 |
构造和析构 | 不支持构造函数和析构函数 | 支持构造函数和析构函数 |
继承和多态 | 不支持继承和多态 | 支持继承和多态 |
默认访问权限 | 所有成员默认公开(public) | 所有成员默认公开(public) |
模板支持 | 不支持模板 | 支持模板 |
作用范围 | 必须在全局或文件作用域声明 | 允许在局部作用域和嵌套结构中声明 |
C 语言中的结构体是一种简单的数据封装工具,仅能包含数据成员,用于将多个相关数据打包成一个复合类型。C 语言不支持构造函数和析构函数,结构体的成员也不能包含函数,主要依靠手动初始化和操作。
相比之下,C++ 中的结构体不仅可以包含数据成员,还可以包含成员函数、构造函数和析构函数,使得结构体可以像类一样组织和管理数据。此外,C++ 支持继承和多态,使得结构体可以参与面向对象编程。C++ 的结构体还可以使用模板定义,支持泛型编程,增强了代码的复用性。
3. C语言的关键字static
和C++的关键字static
有什么区别
static
关键字在 C 和 C++ 中都有,但其具体的含义和用法在两种语言中有所不同,尤其是在 C++ 的面向对象特性下,static
关键字的作用得到了扩展。
1. 变量的存储时长和作用域
C 和 C++ 中的 static
变量存储时长
在 C 和 C++ 中,static
修饰符对变量的存储时长的影响是相同的,它指定变量的生命周期为整个程序运行期间,即静态存储期。即使 static
变量声明在函数内部,它的值也会在函数调用结束后保持,且再次调用该函数时保留先前的值。
-
C 和 C++ 都适用:
void function() { static int count = 0; // 只在首次调用时初始化 count++; printf("Count: %d\n", count); }
每次调用 function()
函数时,count
的值会递增,而不是每次重置为 0。
C 和 C++ 中的 static
变量作用域
-
在文件作用域(全局作用域)中,
static
关键字用于限制变量或函数的作用域,只在当前源文件中可见,避免与其他源文件中的同名符号发生冲突。这在 C 和 C++ 中是相同的。C 和 C++ 都适用:
static int globalVar = 10; // 仅在当前文件内可见
-
总结:在 C 和 C++ 中,
static
修饰的局部变量具有静态存储期,而全局作用域的static
变量只能在其定义的源文件内访问。
2. 类中的静态成员(仅 C++)
静态数据成员
-
C:C 没有类的概念,因此不存在静态数据成员的讨论。
-
C++:在 C++ 中,
static
关键字可以用于类的数据成员,使得这个成员变量属于类本身,而不是类的某个对象。换句话说,静态成员是类级别的,所有对象共享这一份数据,而不是每个对象都有独立的副本。C++ 示例:
class MyClass { public: static int staticVar; // 静态成员变量声明 }; int MyClass::staticVar = 0; // 静态成员变量定义 int main() { MyClass obj1, obj2; MyClass::staticVar = 10; // 通过类名访问静态成员 std::cout << obj1.staticVar << std::endl; // 输出 10 std::cout << obj2.staticVar << std::endl; // 输出 10 }
静态成员函数
-
C:C 中没有成员函数的概念。
-
C++:C++ 还允许定义静态成员函数,这些函数可以访问静态数据成员,但不能访问非静态数据成员,因为静态成员函数不依赖于具体的对象,而是属于整个类。
C++ 示例:
class MyClass {
public:
static int staticVar;
static void staticFunction() {
std::cout << "Static variable: " << staticVar << std::endl;
}
};
int MyClass::staticVar = 42;
int main() {
MyClass::staticFunction(); // 通过类名调用静态函数
return 0;
}
-
总结:C++ 中,
static
可以用于类的静态成员变量和成员函数,使它们与类本身相关联,而不是与具体对象相关联,这是 C 中没有的功能。
3. 静态函数
-
C:在 C 中,如果一个函数被声明为
static
,它只能在定义它的源文件中被调用,其他文件无法访问这个函数。这种用法可以用于限制函数的作用域,避免全局命名空间污染。C 示例:
static void myFunction() { // 仅在当前文件中可见 }
-
C++:C++ 中
static
关键字在普通函数作用域时,与 C 的行为一致,限制函数只在当前文件中可见。此外,C++ 支持静态成员函数(如前述),扩展了static
的用法。
特性 | C 中的 static | C++ 中的 static |
---|---|---|
局部变量的生命周期 | 变量在整个程序运行期间保持值 | 相同,局部变量在整个程序运行期间保持值 |
全局变量的作用域 | 变量只能在当前文件中可见 | 相同,全局变量只能在当前文件中可见 |
静态函数 | 函数只能在当前文件中可见 | 相同,函数只能在当前文件中可见 |
类的静态成员变量 | 不适用 | 支持,属于类,而不是类的具体对象 |
类的静态成员函数 | 不适用 | 支持,属于类,可以访问静态成员变量,不能访问非静态成员 |
静态成员变量初始化 | 不适用 | 需要在类外部进行初始化 |
在 C 和 C++ 中,static
关键字都可以用于局部变量和全局变量,控制变量的生命周期和作用域。局部变量加 static
后可以在多次调用中保留其值,而全局变量或函数加 static
后则限制其作用域为当前文件,防止命名冲突。
在 C++ 中,static
的功能得到了扩展,可以用于类的静态成员变量和静态成员函数。静态成员变量是属于类本身的,而不是某个对象,所有对象共享这一变量。静态成员函数则可以访问静态成员变量,但无法访问非静态的成员变量或函数。
4. C++中,a
和 &a
有什么区别?
1. a
表示变量的值
-
a
代表变量本身的值,无论a
是基本类型(如int
)还是对象,a
都表示该变量的具体内容。例如,对于整数变量a
,a
就是该整数的数值。 -
对于基本类型,
a
的值是直接存储在内存中的数据。例如,a
是一个int
类型的变量时,a
表示该整数值。int a = 10; std::cout << a << std::endl; // 输出变量的值:10
2. &a
表示变量的地址
-
&a
代表变量的地址,即a
在内存中的存储位置。&
是取地址运算符,它返回变量a
的内存地址,而不是变量的值。 -
地址 是一个指针类型的值,表示变量在内存中的存储位置。例如,对于
int a = 10;
,&a
是一个指向a
的指针,表示存储整数10
的那块内存的地址。int a = 10; std::cout << &a << std::endl; // 输出变量的内存地址
3. 内存中存储的差异
a
的值:变量a
的值直接存储在内存中,代表实际的数据内容。&a
的值:&a
表示变量a
的内存地址,该地址是指向存储a
的具体位置的一个数值。
int a = 10;
std::cout << "Value of a: " << a << std::endl; // 输出 a 的值:10
std::cout << "Address of a: " << &a << std::endl; // 输出 a 的地址,例如:0x7ffeefbff5c8
4. 指针与取地址
-
&a
可以赋值给指针,因为指针用来存储变量的地址。指针是一个变量,它保存了另一个变量的内存地址。int a = 10; int* ptr = &a; // 将 a 的地址赋值给指针 ptr std::cout << *ptr << std::endl; // 输出 ptr 指向的变量的值,即 a 的值:10
ptr
是一个指针,保存a
的地址,而*ptr
则表示解引用操作,取出ptr
指向的变量的值,等价于a
。
5. 对象类型的 a
和 &a
对于对象(如类的实例),a
仍然表示对象的实例本身,而 &a
表示该对象在内存中的地址。
class MyClass {
public:
int x;
};
int main() {
MyClass obj;
std::cout << "Object address: " << &obj << std::endl; // 输出对象的地址
return 0;
}
在这个例子中,obj
是一个对象,&obj
是该对象在内存中的地址。
总结
a
:表示变量的值,即变量中存储的数据。&a
:表示变量的地址,即该变量在内存中的位置。
表示内容 | a | &a |
---|---|---|
含义 | 变量的值 | 变量的地址 |
类型 | 变量类型(如 int ) | 指针类型(如 int* ) |
示例(int a = 10; ) | a 表示 10 | &a 表示存储 a 的内存地址 |
使用场景 | 用于直接操作变量的数据 | 用于获取变量的地址,赋值给指针等 |
通过 &a
获取变量的地址可以使程序在指针层次上进行更加灵活的内存操作,例如动态内存管理、函数传参等。
5. C++ 中 #define
和 const
有什么区别
#define
和 const
都可以用于定义常量,但它们在 C++ 中的工作机制、类型检查、作用域、使用场景等方面有很大的区别。下面从几个关键角度详细说明它们的区别。
1. 预处理 vs 编译时常量
-
#define
:#define
是一个预处理指令,用于定义宏常量。在编译之前,预处理器会简单地将代码中的宏名用定义的值进行文本替换。因为它是文本替换,所以没有类型检查,且是在编译前完成的。#define PI 3.14159
当使用
PI
时,编译器并不将它视为变量,而是把所有PI
都替换为3.14159
,就像文本复制粘贴一样。 -
const
:const
是在 C++ 中用于定义一个常量变量的关键词。常量变量在编译时被类型检查,它有明确的类型,不能被修改。const double PI = 3.14159;
PI
是一个double
类型的常量,受到类型检查。如果试图修改PI
,编译器会报错。
2. 类型安全性
-
#define
:#define
仅仅进行文本替换,没有类型安全性。如果使用过程中出现数据类型错误,编译器不会警告或报错。这可能导致不可预知的错误。#define SIZE 10 int array[SIZE]; // 这里 `SIZE` 被直接替换成 `10`
如果宏中包含的表达式复杂,或者与其他操作混合使用时,可能会引发潜在问题。
#define SQUARE(x) x*x int result = SQUARE(4+2); // 实际替换为 4+2*4+2,结果为 14,而不是 36
这个例子说明
#define
并没有对传递的参数进行类型或优先级检查。 -
const
:const
常量有严格的类型检查。编译器会确保常量在使用过程中遵循其定义的类型和操作规则,避免错误的隐式转换或优先级问题。const int SIZE = 10; int array[SIZE]; // 类型安全的编译时检查
如果
SIZE
被用于不兼容类型的地方,编译器会提示错误。
3. 作用域
-
#define
:宏定义没有作用域的概念,它在整个预处理过程中全局有效。如果某个#define
被定义在头文件或多个文件中,可能会导致不可预见的覆盖或冲突问题。#define MAX 100 // MAX 在整个编译单元中有效,容易引发冲突
-
const
:const
常量有与普通变量相同的作用域规则,受限于声明它的范围。可以定义局部或全局的const
常量,并且它们遵循常规的作用域规则,避免命名冲突。const int MAX = 100; // MAX 仅在当前作用域内有效
4. 调试支持
-
#define
:由于宏在编译前被替换成字面值,它们不会出现在调试信息中。调试器无法访问或显示宏值,因为宏不存在于编译后的程序中。 -
const
:const
常量在调试器中是可以查看和访问的,因为它们被编译器当作真正的变量对待,具有类型和存储空间。
5. 内存使用
-
#define
:#define
通过简单的文本替换,它不会在内存中占用空间,因为宏并没有分配变量存储空间。 -
const
:const
常量在某些情况下可能会分配内存,尤其是当它的地址被取用时(即使是局部常量)。不过,在现代 C++ 编译器中,编译器可能会对const
常量进行优化,使其不实际占用内存。
6. 可读性和维护性
-
#define
:由于#define
是纯文本替换,维护起来可能比较困难,特别是当宏名称与变量、函数名称相似时容易造成混淆。并且复杂的宏定义不容易阅读和调试。#define MAX(x, y) ((x) > (y) ? (x) : (y))
如果宏中的表达式有错误,调试和定位问题会比较复杂。
-
const
:const
常量具有明确的类型定义和作用域,易于阅读和维护,且在调试时可以追踪。const int MAX = 100;
7. 表达式支持
-
#define
:#define
可以定义常量,也可以定义表达式或函数宏。例如,定义简单的数学运算表达式或逻辑运算。#define SQUARE(x) ((x) * (x))
-
const
:const
只能定义单个常量值,不能像#define
那样定义表达式或函数宏。
特性 | #define | const |
---|---|---|
定义方式 | 预处理器宏定义 | 编译时常量 |
类型检查 | 无类型检查,纯文本替换 | 有类型检查,符合类型安全性 |
作用域 | 全局作用域,无限定 | 遵循变量作用域规则 |
内存占用 | 无内存占用 | 可能分配内存(取地址时) |
调试支持 | 调试器不可见 | 调试器可见,支持追踪 |
表达式支持 | 支持宏函数与复杂表达式 | 只能定义单个值 |
使用场景 | 简单常量或表达式定义,适合轻量的宏使用 | 类型安全的常量定义,适合代码维护和调试 |
在 C++ 中,#define
和 const
都可以定义常量,但 #define
是通过预处理器实现的简单文本替换,没有类型检查,作用域全局,无法在调试器中追踪。而 const
是编译时的常量,具有类型安全性,可以参与编译时检查,作用域遵循变量规则,并且可以在调试器中看到其值。总体来说,const
提供了更好的类型安全和代码可读性,因此在 C++ 编程中更推荐使用 const
来定义常量,而不是 #define
。
6. 动态链接和静态链接有什么区别
在程序开发中,链接是指将多个目标文件(如源代码编译生成的 .obj
文件或 .o
文件)以及库文件组合在一起,生成可执行文件的过程。链接方式有两种:静态链接和动态链接。它们在链接时机、内存使用、可执行文件大小、灵活性等方面都有显著区别。
1. 定义和基本概念
-
静态链接(Static Linking):在静态链接中,所有依赖的库文件在编译时会被直接嵌入到可执行文件中。链接器会将程序用到的库函数的代码复制到生成的可执行文件内,生成一个完整的独立文件。
-
动态链接(Dynamic Linking):在动态链接中,依赖的库文件不会被嵌入到可执行文件中,而是程序运行时动态加载所需的库(如
.dll
文件或.so
文件)。可执行文件只包含库的引用,程序运行时由操作系统或运行时加载器负责加载库。
2. 链接时机
-
静态链接:链接在编译阶段完成,所有库代码都被包含在可执行文件中,生成的程序独立运行,不再依赖外部库。
-
动态链接:链接在运行时完成。程序运行时,系统会动态加载外部库,加载器在程序执行期间查找并加载所需的共享库。
3. 内存使用和效率
-
静态链接:
- 内存占用:由于每个静态链接的可执行文件都包含所需的库代码,多个程序可能会分别加载相同的库,造成内存浪费。
- 执行效率:静态链接的程序由于不依赖外部库,启动时无需查找和加载动态库,启动速度较快,性能稳定。
-
动态链接:
- 内存占用:多个进程可以共享相同的动态库,这大大减少了内存使用。当多个程序依赖相同的库时,库代码只需加载一次,操作系统会将其共享给不同的进程。
- 执行效率:动态链接的程序需要在运行时查找和加载共享库,导致启动时稍慢一些。加载库的时间会稍微增加启动延迟。
4. 可执行文件大小
-
静态链接:生成的可执行文件较大,因为所有依赖的库代码都被复制到可执行文件中。
-
动态链接:生成的可执行文件较小,因为它不包含库代码,只是引用外部动态库。
5. 灵活性和更新
-
静态链接:
- 更新困难:如果库中的某些代码需要更新(例如安全补丁),则必须重新编译和链接程序,才能生成一个包含更新库代码的新可执行文件。
- 无外部依赖:静态链接生成的可执行文件完全独立,可以在没有动态库的环境下运行,这适合在不提供运行时库支持的系统中使用。
-
动态链接:
- 更新方便:动态库可以单独更新而不需要重新编译程序。只要更新库文件,所有依赖该库的程序都可以自动使用新的库版本。
- 依赖动态库:动态链接的程序在运行时依赖外部库,可能需要确保运行环境中安装了正确版本的动态库。如果找不到或库版本不兼容,程序可能无法运行。
6. 运行时可变性
- 静态链接:
- 固定库版本:库在编译时已经嵌入到可执行文件中,程序始终使用编译时的库版本,无法在运行时更改。
- 动态链接:
- 库版本可变:程序可以在运行时根据需要加载不同版本的库。动态链接的程序允许在运行时灵活选择库,甚至可以在程序运行期间更换库版本。
7. 兼容性与分发
- 静态链接:
- 分发方便:由于可执行文件独立于外部库,可以直接分发和运行,适用于目标系统可能没有预装库的场景。
- 动态链接:
- 分发需要库支持:动态链接的可执行文件需要目标系统提供必要的动态库支持。如果系统中缺少所需的库,用户可能需要手动安装,增加分发和安装的复杂性。
8. 错误处理和调试
- 静态链接:
- 错误排查简单:由于所有代码都包含在可执行文件中,调试时不需要关注外部库版本的问题。
- 动态链接:
- 运行时错误:如果动态库在程序运行时不可用、版本不兼容或者存在其他问题,程序可能会在启动或运行过程中失败。调试此类问题时,需要检查库是否正确安装、库版本是否兼容等。
特性 | 静态链接 | 动态链接 |
---|---|---|
链接时机 | 编译时 | 运行时 |
内存占用 | 较高,每个程序包含完整库代码 | 较低,多个进程共享动态库 |
可执行文件大小 | 较大,包含所有依赖的库代码 | 较小,只包含库引用 |
更新灵活性 | 更新库时需要重新编译整个程序 | 更新库时只需替换动态库 |
启动时间 | 较快,无需加载外部库 | 较慢,运行时加载库 |
分发 | 独立,可直接分发运行 | 依赖动态库,分发时需确保库版本一致 |
调试 | 简单,所有代码在一个文件中 | 复杂,可能涉及动态库版本或加载错误 |
内存效率 | 较低,每个程序都有一份库副本 | 高,多个进程共享库 |
运行时灵活性 | 固定库版本,无法在运行时切换 | 库版本可变,运行时可选择不同版本 |
-
静态链接:所有库代码在编译时就被嵌入到可执行文件中,生成的程序独立运行,不依赖外部库,启动速度快,但可执行文件较大且内存效率较低。如果库发生更新,程序必须重新编译。
-
动态链接:库代码在运行时加载,多个进程可以共享相同的动态库,内存效率更高,生成的可执行文件较小,更新库时无需重新编译程序。然而,程序启动时可能会稍慢一些,并且依赖于正确的库配置和版本匹配。
7.C++ 中 typedef
和 #define
有什么区别
1. 定义目的
-
typedef
:typedef
用于为已有的类型创建新的类型别名。它不创建新的类型,只是为某种类型指定一个别名,以便在代码中更简洁地引用复杂的类型。typedef unsigned int uint; // 为 unsigned int 定义一个别名 uint
-
#define
:#define
是一个预处理器宏,可以用于定义符号常量、表达式、简单的函数宏等。它通过文本替换的方式在预处理阶段将符号替换为其定义的内容。#define uint unsigned int // 通过文本替换为 unsigned int 定义符号 uint
2. 类型检查
-
typedef
:typedef
生成的类型别名在编译时受到类型检查。编译器能够准确识别typedef
定义的别名,并进行类型安全的操作。例如,uint
被编译器视为unsigned int
,并可以享受所有的类型检查机制。typedef unsigned int uint; uint x = 10; // 正常,x 是 unsigned int
-
#define
:#define
是纯粹的文本替换,不会进行任何类型检查。它只是在预处理阶段将符号替换为相应的定义内容,编译器并不知道这是类型替换,可能会引发潜在的错误或意外行为。#define uint unsigned int uint x = 10; // 实际被替换为 unsigned int x = 10;
虽然表面看起来和
typedef
的效果相似,但宏并没有类型检查。
3. 语法和表达能力
-
typedef
:typedef
只能为类型创建别名,不能用于定义常量或函数宏。它的表达能力受限于类型系统,不能像#define
那样灵活地替换任意文本。 -
#define
:#define
不仅可以用于类型别名,还可以用于定义常量、表达式和函数宏。它具有更灵活的文本替换功能,但这也容易引入潜在的错误。
4. 作用域
-
typedef
:typedef
别名在定义它的作用域内有效,遵循 C++ 语言的作用域规则。它可以在局部作用域、全局作用域或类中使用,受限于编译器的作用域控制。 -
#define
:#define
没有作用域的概念,一旦定义,在整个预处理期间都有效。它的影响是全局的,除非使用#undef
显式取消定义。由于宏在预处理阶段生效,跨文件作用域时可能产生意外影响。
5. 编译期 vs 预处理期
-
typedef
:typedef
是在编译期生效的。它只影响类型的声明和定义,不会影响预处理过程。 -
#define
:#define
是在预处理期生效的。所有的符号替换都在预处理阶段完成,编译器接收到的是已经替换过的文本代码。这个文本替换的性质也可能带来一些潜在问题。
6. 调试和可读性
-
typedef
:由于typedef
是类型别名,调试器在调试过程中会将别名映射回原始类型,可以正确追踪变量的类型和行为,因此调试时typedef
更加直观和友好。 -
#define
:因为#define
是文本替换,在调试过程中宏的定义通常不会直接出现在调试信息中。调试器只会显示宏替换后的代码,因此可能使调试变得困难,尤其是涉及复杂的宏时。
7. 模板与复杂类型支持
-
typedef
:typedef
在 C++ 中能够处理复杂类型,包括模板类型、指针、函数指针等。typedef int* intptr; typedef void(*funcptr)(int); // 函数指针别名
它也可以与 C++ 的模板结合使用,使代码更加简洁和易于维护。
template<typename T> typedef T* ptr;
-
#define
:#define
没有能力处理复杂的模板类型或作用于复杂的类型表达式。虽然可以通过宏定义一些简单的指针类型别名,但容易出现意外问题。#define intptr int* #define funcptr void(*)(int)
特性 | typedef | #define |
---|---|---|
定义目的 | 为类型创建别名 | 文本替换,定义常量、宏或别名 |
类型检查 | 有类型检查,受编译器约束 | 无类型检查,纯文本替换 |
语法能力 | 只能定义类型别名 | 可以定义常量、表达式和函数宏 |
作用域 | 遵循 C++ 作用域规则 | 无作用域概念,全局有效 |
编译时机 | 编译期生效 | 预处理期生效,编译器之前替换 |
调试支持 | 调试器友好,类型信息可见 | 调试信息中不可见,容易混淆 |
模板支持 | 支持复杂类型和模板 | 不支持模板或复杂类型表达式 |
-
typedef
:用于为类型创建别名,具有类型安全性,遵循编译器的类型检查规则和作用域规则,能够处理复杂类型和模板,适用于清晰、易维护的代码。typedef
只影响类型,不会影响常量或宏的定义,编译时生效,并在调试时容易追踪类型。 -
#define
:是一个预处理器指令,使用纯文本替换方式,可以定义常量、宏和简单类型别名。#define
不进行类型检查,灵活但容易产生错误,且不具备typedef
那样的类型安全和复杂类型支持。由于它是在预处理阶段生效,调试时也难以追踪。