文章目录
- C++ 编程语言术语和概念总结
- 1. **程序结构**
- 2. **词汇与语法**
- 3. **实体**
- 4. **声明与定义**
- 5. **函数**
- 6. **名称查找与作用域**
- 7. **类型系统**
- 8. **变量**
- 准则: C++ 程序的组成与翻译过程
- C++ 程序的组成与翻译过程
- 1. **C++ 程序的组成**
- 2. **C++ 程序的翻译过程**
- 2.1 **预处理(Preprocessing)**
- 2.2 **编译(Compilation)**
- 2.3 **汇编(Assembly)**
- 2.4 **链接(Linking)**
- 2.5 **执行(Execution)**
- 3. **C++ 程序的执行流程**
- 4. **C++ 程序的入口点:`main` 函数**
- 5. **总结**
- 准则:关键字、标识符、注释、字面量和转义序列
- C++ 程序中的关键字、标识符、注释、字面量和转义序列
- 1. **关键字(Keywords)**
- 常见的关键字:
- 示例:
- 2. **标识符(Identifiers)**
- 标识符的命名规则:
- 示例:
- 3. **注释(Comments)**
- 注释的类型:
- 示例:
- 4. **字面量(Literals)**
- 常见的字面量类型:
- 示例:
- 5. **转义序列(Escape Sequences)**
- 常见的转义序列:
- 示例:
- 总结
- 准则:实体
- C++ 程序中的实体及其分类
- 1. **值(Value)**
- 2. **对象(Object)**
- 3. **引用(Reference)**
- 4. **结构化绑定(Structured Binding,自 C++17 起)**
- 5. **函数(Function)**
- 6. **枚举器(Enumerator)**
- 7. **类型(Type)**
- 8. **类成员(Class Member)**
- 9. **模板(Template)**
- 10. **模板特化(Template Specialization)**
- 11. **参数包(Parameter Pack,自 C++11 起)**
- 12. **命名空间(Namespace)**
- 13. **预处理器宏(Preprocessor Macro)**
- 总结
- 准则:声明与定义
- C++ 中的声明与定义:ODR(One Definition Rule)规则
- 1. **声明(Declaration)**
- 2. **定义(Definition)**
- 3. **ODR(One Definition Rule,单一定义规则)**
- 4. **声明 vs 定义的例子**
- 5. **ODR 的例外情况**
- 6. **ODR 违反的后果**
- 7. **示例:ODR 违反的情况**
- 8. **总结**
- 准则: 函数的定义与语句序列中的表达式
- 函数的定义与语句序列中的表达式
- 1. **函数的基本结构**
- 2. **语句序列**
- 3. **表达式**
- 4. **表达式在语句中的应用**
- 5. **示例:计算矩形的面积和周长**
- 6. **总结**
- 准则: C++ 中的名称查找、作用域与链接详解
- 1. **名称查找(Name Lookup)**
- 名称查找的规则:
- 示例:
- 2. **作用域(Scope)**
- 示例:
- 3. **链接(Linkage)**
- 链接的类型:
- 示例:
- 4. **名称的作用域与链接的关系**
- 总结
- 准则:变量范畴
- 1. **对象与引用**
- 2. **非静态数据成员**
- 3. **为什么非静态数据成员不是变量**
- 4. **变量的特性**
- 5. **示例**
- 总结
- 准则 :类型关联
- 1. **基本类型(Primitive Types)**
- 2. **复合类型(Compound Types)**
- 3. **用户定义类型(User-Defined Types)**
- 4. **完整类型(Complete Types)与不完整类型(Incomplete Types)**
- 5. **类型的作用**
- 6. **类型推导**
- 总结
C++ 编程语言术语和概念总结
C++ 是一种复杂的编程语言,包含了许多特定的术语和概念。以下是这些术语和概念的简要总结:
1. 程序结构
- 文本文件:C++ 程序由一系列文本文件组成,通常包括头文件(
.h或.hpp)和源文件(.cpp)。这些文件包含了程序的声明和定义。 - 翻译:编译器将这些文件翻译成机器代码或中间表示,最终生成可执行程序。
- main 函数:每个C++程序至少有一个
main函数,它是程序的入口点。
2. 词汇与语法
- 关键字:具有特殊含义的保留字,如
int,class,template等,不能用作标识符。 - 标识符:用户自定义的名字,用于命名变量、函数、类等。必须符合命名规则且不与关键字冲突。
- 注释:用于解释代码,被编译器忽略。单行注释以
//开始,多行注释以/* ... */包围。 - 字面量:直接在代码中出现的值,如数字、字符串、字符等。其值由字符集和编码确定。
- 转义序列:用于表示某些特殊字符,如
\n(换行),\t(制表),\\(反斜杠)等。
3. 实体
C++ 程序中的实体是程序的基本组成部分,包括但不限于:
- 值:可以存储在对象中的数据。
- 对象:具有类型和存储空间的实体,可以保存值。
- 引用:是对另一个对象的别名。
- 结构化绑定(自 C++17 起):允许从初始化表达式中解构出多个值。
- 函数:执行特定任务的代码块。
- 枚举器:枚举类型的成员。
- 类型:定义了数据的格式和可以对其执行的操作。
- 类成员:类内部的变量和函数。
- 模板:用于定义泛型函数或类。
- 模板特化:为特定类型提供不同的实现。
- 参数包(自 C++11 起):变参模板的一部分,表示不定数量的参数。
- 命名空间:用于组织代码,避免名称冲突。
4. 声明与定义
- 声明:引入实体并指定其属性(如类型),但不一定分配存储空间。
- 定义:除了声明外,还分配必要的存储空间,并可能初始化实体。对于非内联的函数和变量,一个程序中只能有一个定义。
- ODR 使用(One Definition Rule):确保在程序中,每个非内联的函数或变量只有一个定义。
5. 函数
- 函数定义:包含函数体,即一系列语句,其中可能包含表达式来执行计算。
- 语句:构成函数体的命令,控制程序的流程。
- 表达式:由操作数和运算符组成的组合,用于计算值。
6. 名称查找与作用域
- 名称查找:当程序遇到一个名称时,编译器会搜索该名称对应的声明。
- 作用域:名称在程序中有效的区域。例如,局部变量的作用域限于它所在的函数或代码块。
- 链接:某些名称(如全局变量或函数)可以在不同作用域或翻译单元中引用同一个实体,这称为外部链接;而其他名称仅在其定义的作用域内可见,称为内部链接。
7. 类型系统
- 类型:每个对象、引用、函数和表达式都关联着一个类型。类型可以是基本类型(如
int,double)、复合类型(如数组、指针、类)或用户定义类型。 - 完整类型:已完全定义的类型,所有信息都可用。
- 不完整类型:部分定义的类型,如前向声明的类,某些信息不可用。
8. 变量
- 变量:声明的对象或引用,只要它们不是非静态数据成员。变量可以在程序的不同部分中存储和操作数据。
准则: C++ 程序的组成与翻译过程
C++ 程序的组成与翻译过程
C++ 程序由一系列文本文件(通常是头文件和源文件)组成,这些文件包含声明、定义和其他代码。程序经过多个阶段的翻译,最终生成可执行程序。当 C++ 实现调用其 main 函数时,程序开始执行。
1. C++ 程序的组成
C++ 程序通常由以下两类文件组成:
-
头文件(Header Files):扩展名为
.h或.hpp。头文件主要用于声明类、函数、变量、宏等,以便在多个源文件中共享这些声明。头文件通常不包含实现代码,而是提供接口供其他文件使用。- 作用:头文件用于定义接口,确保不同源文件之间的代码可以正确协作。
- 示例:
// myheader.h #ifndef MYHEADER_H #define MYHEADER_H// 声明一个函数 void greet();// 声明一个类 class MyClass { public:void print(); };#endif // MYHEADER_H
-
源文件(Source Files):扩展名为
.cpp或.cc。源文件包含函数的实现、类的成员函数定义以及其他具体的代码逻辑。每个源文件可以包含对头文件的#include指令,以引入所需的声明。- 作用:源文件包含程序的实际实现代码,定义了如何执行特定的任务。
- 示例:
// main.cpp #include "myheader.h" #include <iostream>// 定义 greet 函数 void greet() {std::cout << "Hello, World!" << std::endl; }// 定义 MyClass 的成员函数 void MyClass::print() {std::cout << "This is MyClass" << std::endl; }int main() {greet(); // 调用 greet 函数MyClass obj;obj.print(); // 调用 MyClass 的成员函数return 0; }
2. C++ 程序的翻译过程
C++ 程序的翻译过程分为多个阶段,从源代码到可执行程序的生成涉及预处理、编译、汇编和链接等步骤。以下是详细的翻译过程:
2.1 预处理(Preprocessing)
预处理器是编译器的第一步,它处理源文件中的预处理指令(如 #include, #define, #ifdef 等)。预处理器会将头文件的内容插入到源文件中,并替换宏定义。预处理后的代码是一个没有预处理指令的纯C++代码文件。
- 作用:预处理器负责处理头文件的包含、宏替换等任务,确保所有必要的声明和定义都包含在源文件中。
- 示例:
#include "myheader.h":将myheader.h文件的内容插入到当前源文件中。#define PI 3.14159:将所有出现的PI替换为3.14159。
2.2 编译(Compilation)
编译器将预处理后的源代码转换为中间表示(通常是汇编代码或目标代码)。编译器会检查代码的语法和语义错误,并生成每个源文件对应的目标文件(扩展名为 .o 或 .obj)。目标文件包含机器码,但它们还不是可以直接执行的程序,因为它们可能依赖于其他文件中的符号(如函数或变量)。
- 作用:编译器将C++代码转换为低级的机器码或汇编代码,并进行初步的错误检查。
- 示例:
- 编译
main.cpp生成main.o。 - 编译
myclass.cpp生成myclass.o。
- 编译
2.3 汇编(Assembly)
如果编译器生成的是汇编代码,那么接下来的步骤是将汇编代码转换为目标代码。汇编器将汇编语言指令转换为机器码,生成目标文件。现代编译器通常直接生成目标文件,跳过显式的汇编步骤。
- 作用:汇编器将汇编代码转换为机器码,生成目标文件。
- 示例:
- 汇编
main.s生成main.o。
- 汇编
2.4 链接(Linking)
链接器将多个目标文件和库文件组合在一起,生成最终的可执行程序。链接器的主要任务是解析各个目标文件之间的符号引用(如函数调用、全局变量等),并将它们绑定到实际的地址上。链接器还会将标准库或其他外部库中的函数和数据链接到程序中。
- 作用:链接器将多个目标文件和库文件合并,生成最终的可执行程序。
- 示例:
- 链接
main.o和myclass.o,生成可执行文件program.exe。 - 链接器还会链接标准库(如
iostream)中的函数。
- 链接
2.5 执行(Execution)
当可执行程序生成后,C++ 实现(即操作系统或运行时环境)会调用程序的 main 函数作为入口点,开始执行程序。main 函数是C++程序的起点,所有其他代码都是通过 main 函数调用的。
- 作用:
main函数是程序的入口点,控制程序的执行流程。 - 示例:
- 当用户运行
program.exe时,操作系统会加载该程序并调用main函数。
- 当用户运行
3. C++ 程序的执行流程
C++ 程序的执行流程如下:
- 预处理器处理源文件中的预处理指令,生成扩展后的源代码。
- 编译器将扩展后的源代码编译为目标代码(或汇编代码)。
- 汇编器(如果需要)将汇编代码转换为目标代码。
- 链接器将多个目标文件和库文件链接在一起,生成最终的可执行程序。
- C++ 实现调用
main函数,程序开始执行。
4. C++ 程序的入口点:main 函数
main 函数是C++程序的入口点,它是程序启动时首先执行的函数。main 函数的签名有两种常见的形式:
-
带参数的
main函数:int main(int argc, char* argv[]) {// argc: 命令行参数的数量// argv: 命令行参数的数组return 0; }argc是命令行参数的数量(包括程序名)。argv是一个指向字符串数组的指针,每个字符串是命令行参数。
-
不带参数的
main函数:int main() {// 简单的 main 函数return 0; }
main 函数必须返回一个整数值,通常返回 0 表示程序成功结束,非零值表示程序遇到了错误。
5. 总结
- C++ 程序的组成:C++ 程序由头文件和源文件组成,头文件包含声明,源文件包含实现。
- 翻译过程:C++ 程序的翻译过程包括预处理、编译、汇编和链接四个主要阶段,最终生成可执行程序。
- 执行流程:当C++实现调用
main函数时,程序开始执行,main函数是程序的入口点。 main函数:main函数是C++程序的起点,控制程序的执行流程,必须返回一个整数值。
通过理解C++程序的组成和翻译过程,你可以更好地组织代码结构,编写高效的C++程序,并解决编译和链接过程中可能出现的问题。
准则:关键字、标识符、注释、字面量和转义序列
C++ 程序中的关键字、标识符、注释、字面量和转义序列
在C++中,程序的语法由关键字、标识符、注释、字面量和转义序列等元素组成。这些元素共同决定了程序的结构和语义。下面将详细解释每个概念,并通过示例说明它们的作用。
1. 关键字(Keywords)
关键字是C++语言中具有特殊含义的保留字,它们用于定义语言的语法和结构。关键字不能用作标识符(如变量名、函数名等),因为它们有固定的用途。C++ 标准库定义了许多关键字,随着标准的演进,新的关键字也可能被引入。
常见的关键字:
- 控制结构:
if,else,for,while,do,switch,case,break,continue - 类型修饰符:
const,volatile,mutable - 存储类说明符:
auto,register,static,extern,thread_local - 访问控制:
public,private,protected - 类和对象:
class,struct,union,enum,typename - 函数特性:
inline,virtual,override,final - 模板:
template,typename,decltype - 异常处理:
try,catch,throw - 内存管理:
new,delete - 其他:
namespace,using,friend,operator,this
示例:
int main() {if (true) {std::cout << "Hello, World!" << std::endl;}return 0;
}
在这个例子中,if 是一个关键字,用于控制条件分支。
2. 标识符(Identifiers)
标识符是程序员定义的名称,用于命名变量、函数、类、枚举器等实体。标识符必须遵循一定的规则,并且不能与关键字冲突。标识符可以包含字母、数字和下划线,但不能以数字开头。
标识符的命名规则:
- 必须以字母或下划线开头。
- 只能包含字母、数字和下划线。
- 区分大小写(
myVar和myvar是不同的标识符)。 - 不能是C++的关键字。
示例:
int myVariable = 42; // 合法的标识符
double pi_value = 3.14; // 合法的标识符
void printMessage() { /* ... */ } // 函数名是合法的标识符
3. 注释(Comments)
注释是程序中用于解释代码的文本,它们不会影响程序的执行,编译器在翻译过程中会忽略注释。注释的主要作用是提高代码的可读性和可维护性,帮助开发者理解代码的功能和逻辑。
注释的类型:
- 单行注释:使用
//开头,注释从//开始到该行结束。 - 多行注释:使用
/* ... */包围,注释可以跨越多行。
示例:
// 这是一个单行注释/** 这是一个多行注释* 可以跨越多行*/int x = 10; // 单行注释可以放在代码后面/** 多行注释可以包围代码块*/
int y = 20;
4. 字面量(Literals)
字面量是直接出现在程序中的常量值,表示具体的数值、字符、字符串或其他类型的值。字面量的值由字符集和编码确定,具体取决于编译器和平台的设置。
常见的字面量类型:
-
整数字面量:表示整数,可以是十进制、八进制、十六进制或二进制。
- 十进制:
42 - 八进制:
052(以0开头) - 十六进制:
0x2A(以0x开头) - 二进制:
0b101010(以0b开头)
- 十进制:
-
浮点字面量:表示浮点数,可以是小数形式或科学计数法。
- 小数形式:
3.14 - 科学计数法:
6.02e23
- 小数形式:
-
字符字面量:表示单个字符,用单引号
' '包围。字符的值由字符集和编码确定。- 普通字符:
'A' - 转义字符:
'\n'(换行)
- 普通字符:
-
字符串字面量:表示字符串,用双引号
" "包围。字符串可以包含多个字符。- 普通字符串:
"Hello, World!" - 包含转义字符的字符串:
"C:\\Program Files\\MyApp"
- 普通字符串:
-
布尔字面量:表示布尔值,只有两个可能的值:
true和false。 -
空指针字面量:
nullptr,表示空指针。
示例:
int x = 42; // 整数字面量
double pi = 3.14; // 浮点字面量
char ch = 'A'; // 字符字面量
std::string message = "Hello"; // 字符串字面量
bool flag = true; // 布尔字面量
int* ptr = nullptr; // 空指针字面量
5. 转义序列(Escape Sequences)
转义序列用于表示某些特殊字符,这些字符在普通文本中无法直接输入,或者有特殊的含义(如换行、制表符等)。转义序列以反斜杠 \ 开头,后跟一个或多个字符,表示特定的字符。
常见的转义序列:
\n:换行符(newline)\t:水平制表符(tab)\r:回车符(carriage return)\b:退格符(backspace)\f:换页符(form feed)\v:垂直制表符(vertical tab)\\:反斜杠本身\':单引号\":双引号\?:问号\0:空字符(null character)\xNN:十六进制表示的字符(N 是十六进制数字)\uNNNN:16位 Unicode 字符\UNNNNNNNN:32位 Unicode 字符
示例:
std::cout << "Hello\nWorld"; // 换行
std::cout << "First\tSecond"; // 制表符
std::cout << "Path: C:\\Windows\\System32"; // 反斜杠
std::cout << "He said, \"Hello!\""; // 双引号
std::cout << "Caf\u00E9"; // Unicode 字符 'é'
总结
- 关键字:是C++语言中具有特殊含义的保留字,不能用作标识符。它们用于定义语言的语法和结构。
- 标识符:是由程序员定义的名称,用于命名变量、函数、类等实体。标识符必须遵循命名规则,并且不能与关键字冲突。
- 注释:是用于解释代码的文本,编译器在翻译过程中会忽略注释。注释分为单行注释和多行注释。
- 字面量:是直接出现在程序中的常量值,表示具体的数值、字符、字符串等。字面量的值由字符集和编码确定。
- 转义序列:用于表示某些特殊字符,这些字符在普通文本中无法直接输入,或者有特殊的含义。转义序列以反斜杠
\开头,后跟一个或多个字符。
通过理解这些概念,你可以更好地编写符合C++语法规范的代码,并确保代码的可读性和可维护性。
准则:实体
C++ 程序中的实体及其分类
在C++中,**实体(Entity)**是指程序中可以被命名和引用的任何元素。这些实体是程序的基本组成部分,它们定义了数据、行为和结构。C++程序的实体可以分为以下几类:
- 值(Value)
- 对象(Object)
- 引用(Reference)
- 结构化绑定(Structured Binding,自 C++17 起)
- 函数(Function)
- 枚举器(Enumerator)
- 类型(Type)
- 类成员(Class Member)
- 模板(Template)
- 模板特化(Template Specialization)
- 参数包(Parameter Pack,自 C++11 起)
- 命名空间(Namespace)
此外,预处理器宏虽然在编译前处理阶段会影响代码,但它们不是C++语言中的正式实体。
下面将详细解释每一类实体,并说明它们在C++程序中的作用。
1. 值(Value)
值是程序中可以存储在对象中的具体数据。值可以是基本类型的字面量(如整数、浮点数、字符等),也可以是复杂类型(如类对象、数组等)中的数据。
- 特点:值是不可变的,表示某个特定的数据内容。
- 示例:
int x = 42; // 42 是一个整数值 double pi = 3.14; // 3.14 是一个双精度浮点数值 char ch = 'A'; // 'A' 是一个字符值
2. 对象(Object)
对象是具有类型和存储空间的实体,它可以存储值。对象可以是基本类型(如 int, double),也可以是复杂类型(如类、数组、指针等)。每个对象都有自己的内存地址,可以在程序中进行读取、写入和操作。
- 特点:对象有生命周期,它的存在时间取决于其作用域和存储类别。
- 示例:
int x = 42; // x 是一个整数对象 std::string name = "Alice"; // name 是一个字符串对象
3. 引用(Reference)
引用是对另一个对象的别名,它必须在声明时初始化,并且一旦初始化后不能改变所引用的对象。引用本身没有独立的存储空间,它只是对现有对象的另一种访问方式。
- 特点:引用必须绑定到一个有效的对象,不能为
nullptr。 - 示例:
int x = 42; int& ref = x; // ref 是对 x 的引用 ref = 100; // 修改 x 的值
4. 结构化绑定(Structured Binding,自 C++17 起)
结构化绑定允许你从一个复合类型(如 std::pair, std::tuple, 或类对象)中解构出多个值,并将它们绑定到多个变量上。这使得代码更加简洁和易读。
- 特点:适用于 C++17 及以后的标准,简化了从复合类型中提取多个值的过程。
- 示例:
std::pair<int, double> p = {10, 3.14}; auto [a, b] = p; // a = 10, b = 3.14
5. 函数(Function)
函数是一段封装了特定任务的代码,它可以根据需要被调用。函数可以接受参数并返回结果。C++ 中的函数可以是全局函数、类成员函数、静态成员函数、内联函数等。
- 特点:函数定义了程序的行为,可以通过名称调用。
- 示例:
int add(int a, int b) {return a + b; }
6. 枚举器(Enumerator)
枚举器是枚举类型中的常量成员。枚举类型用于定义一组命名的常量,通常用于表示有限的选项或状态。
- 特点:枚举器是枚举类型的一部分,它们的值是整数,但通常使用符号名称来提高可读性。
- 示例:
enum Color { Red, Green, Blue }; Color myColor = Red; // 使用枚举器
7. 类型(Type)
类型定义了数据的格式、可以对其执行的操作以及数据的存储方式。C++ 中的类型可以是基本类型(如 int, double)、复合类型(如数组、指针、类)、用户定义类型(如结构体、类、枚举)等。
- 特点:类型决定了对象的内存布局和可以对其执行的操作。
- 示例:
int x; // 基本类型 std::vector<int> vec; // 复合类型 struct Point { int x, y; }; // 用户定义类型
8. 类成员(Class Member)
类成员是类中的变量或函数,它们属于类的实例或类本身。类成员可以是公有、保护或私有的,控制其访问权限。
- 特点:类成员是类的一部分,可以通过类的对象或类名访问(对于静态成员)。
- 示例:
class Rectangle { public:int width; // 公有成员变量int height; // 公有成员变量void setDimensions(int w, int h); // 成员函数 };Rectangle rect; rect.width = 10; // 访问成员变量 rect.setDimensions(10, 5); // 调用成员函数
9. 模板(Template)
模板是C++中的泛型编程工具,允许编写与类型无关的代码。模板可以用于函数和类,使得相同的代码可以应用于不同的类型。
- 特点:模板在编译时生成具体的类型实例,支持类型参数化。
- 示例:
template<typename T> T max(T a, T b) {return (a > b) ? a : b; }int result = max(10, 20); // 实例化为 int 类型 double d_result = max(3.14, 2.71); // 实例化为 double 类型
10. 模板特化(Template Specialization)
模板特化允许为特定类型提供不同的实现。通过特化,你可以为某些特定类型定制模板的行为,而不需要修改通用模板的逻辑。
- 特点:模板特化提供了针对特定类型的优化或特殊处理。
- 示例:
template<typename T> T max(T a, T b) {return (a > b) ? a : b; }// 特化 max 函数,针对 char 类型 template<> char max<char>(char a, char b) {return (a > b) ? a : b; }
11. 参数包(Parameter Pack,自 C++11 起)
参数包是变参模板的核心概念,允许函数或类模板接受不定数量的参数。参数包可以用递归展开的方式处理多个参数,支持编写灵活的泛型代码。
- 特点:参数包允许模板接受任意数量的参数,支持递归展开。
- 示例:
template<typename... Args> void print(Args... args) {(std::cout << ... << args) << std::endl; }print(1, 2.5, "Hello"); // 打印多个参数
12. 命名空间(Namespace)
命名空间用于组织代码,避免不同部分的标识符冲突。命名空间可以嵌套,形成层次结构,帮助管理大型项目中的命名问题。
- 特点:命名空间中的名称不会与其他命名空间中的同名标识符冲突。
- 示例:
namespace math {int add(int a, int b) {return a + b;} }namespace geometry {double area(double radius) {return 3.14 * radius * radius;} }int main() {std::cout << math::add(10, 20) << std::endl; // 调用 math 命名空间中的 addstd::cout << geometry::area(5.0) << std::endl; // 调用 geometry 命名空间中的 area }
13. 预处理器宏(Preprocessor Macro)
预处理器宏是通过预处理器指令(如 #define)定义的符号替换规则。预处理器在编译之前处理这些宏,将它们替换为实际的代码或文本。虽然宏在编译前处理阶段影响代码,但它们不是C++语言中的正式实体,因此不在C++的实体分类中。
- 特点:宏是文本替换,不遵循C++的语法规则,可能导致难以调试的错误。
- 示例:
#define PI 3.14159 #define SQUARE(x) ((x) * (x))int main() {double area = PI * SQUARE(5.0); // 使用宏 }
总结
C++ 程序的实体是程序中可以被命名和引用的元素,它们定义了数据、行为和结构。C++ 中的实体可以分为以下几类:
- 值:表示具体的数据内容。
- 对象:具有类型和存储空间的实体,可以存储值。
- 引用:对另一个对象的别名。
- 结构化绑定:从复合类型中解构多个值。
- 函数:封装了特定任务的代码。
- 枚举器:枚举类型中的常量成员。
- 类型:定义了数据的格式和操作。
- 类成员:类中的变量或函数。
- 模板:泛型编程工具,支持类型参数化。
- 模板特化:为特定类型提供不同的实现。
- 参数包:变参模板的核心概念,支持不定数量的参数。
- 命名空间:用于组织代码,避免命名冲突。
预处理器宏虽然在编译前处理阶段影响代码,但它们不是C++语言中的正式实体,因此不在上述分类中。
通过理解这些实体及其分类,你可以更好地设计和编写C++程序,确保代码的清晰性、可维护性和正确性。
准则:声明与定义
C++ 中的声明与定义:ODR(One Definition Rule)规则
在C++中,声明和定义是两个密切相关但有所区别的概念。它们共同决定了程序中的实体(如变量、函数、类等)如何被引入并使用。特别是,C++ 强制执行 ODR(One Definition Rule,单一定义规则),确保每个非内联函数或变量在程序中只有一个定义。这有助于避免重复定义导致的二义性和冲突。
1. 声明(Declaration)
声明 是一种语句,它引入了一个实体,并将其与一个名称关联起来。声明告诉编译器该实体的存在及其类型,但不一定分配存储空间或初始化实体。声明可以多次出现在不同的作用域中,只要它们一致即可。
- 作用:声明使编译器知道某个实体的类型和名称,从而可以在后续代码中正确使用它。
- 特点:
- 声明不需要分配内存。
- 声明可以多次出现,但必须保持一致。
- 声明通常用于头文件中,以便多个源文件可以共享同一个实体。
2. 定义(Definition)
定义 不仅引入了实体,还为该实体分配了存储空间,并可能对其进行初始化。定义是声明的一种特殊形式,它提供了实体的所有必需属性,使得该实体可以在程序中被实际使用。
- 作用:定义不仅告诉编译器实体的存在,还为其分配内存并初始化(如果需要)。定义是唯一的,程序中只能有一个定义。
- 特点:
- 定义分配内存。
- 定义只能出现一次(对于非内联函数和变量)。
- 定义通常出现在源文件中,而不是头文件中。
3. ODR(One Definition Rule,单一定义规则)
ODR 是 C++ 中的一个重要规则,它规定了程序中某些实体(如非内联函数、变量、类等)只能有一个定义。具体来说:
- 非内联函数:程序中只能有一个非内联函数的定义。如果多个翻译单元(即不同的源文件)中都包含了同一个非内联函数的定义,链接器将报错。
- 变量:程序中只能有一个非静态局部变量或全局变量的定义。对于全局变量,如果多个翻译单元中都包含了同一个变量的定义,链接器也会报错。
- 类和模板:类和模板的定义可以在多个翻译单元中出现,但它们必须完全一致。模板实例化时,每个实例化也必须遵循 ODR。
ODR 的目的是确保程序中每个实体的行为和状态是唯一且一致的,避免因重复定义而导致的二义性或冲突。
4. 声明 vs 定义的例子
// 头文件 (header.h)
#ifndef HEADER_H
#define HEADER_H// 声明:引入函数和变量,但不分配内存
extern int globalVar; // 全局变量的声明
void myFunction(); // 函数的声明#endif
// 源文件 (source.cpp)
#include "header.h"// 定义:为全局变量分配内存
int globalVar = 42;// 定义:实现函数
void myFunction() {std::cout << "Hello, World!" << std::endl;
}
在这个例子中:
extern int globalVar;是一个声明,它告诉编译器globalVar是一个全局变量,但没有为其分配内存。int globalVar = 42;是一个定义,它为globalVar分配了内存并初始化为 42。void myFunction();是一个声明,它告诉编译器myFunction是一个函数,但没有提供其实现。void myFunction()是一个定义,它实现了myFunction,并为其分配了内存。
5. ODR 的例外情况
ODR 规则有一些例外情况,允许某些实体在多个翻译单元中定义多次,而不会违反 ODR:
- 内联函数:内联函数可以在多个翻译单元中定义多次,但所有定义必须完全一致。编译器会确保这些定义只生成一份代码。
- 常量表达式:
constexpr变量可以在多个翻译单元中定义多次,但所有定义必须相同。 - 模板:模板的定义可以在多个翻译单元中出现,因为模板本身不是实体,而是生成实体的模板。模板实例化时,每个实例化必须遵循 ODR。
- 匿名命名空间中的实体:匿名命名空间中的实体具有内部链接,因此它们不会违反 ODR,即使在多个翻译单元中定义。
6. ODR 违反的后果
如果违反了 ODR,可能会导致以下问题:
- 链接错误:如果多个翻译单元中定义了同一个非内联函数或全局变量,链接器会报错,指出存在重复定义。
- 未定义行为:如果多个定义不一致(例如,函数签名不同或变量初始化值不同),程序可能会表现出未定义行为,导致难以调试的错误。
- 性能问题:即使程序能够编译和链接成功,多个定义可能会导致不必要的代码膨胀或性能下降。
7. 示例:ODR 违反的情况
假设我们有两个源文件 file1.cpp 和 file2.cpp,它们都定义了同一个全局变量 globalVar:
// file1.cpp
int globalVar = 10;// file2.cpp
int globalVar = 20;
在这种情况下,链接器会报错,指出 globalVar 在多个翻译单元中定义,违反了 ODR。
为了修复这个问题,我们可以使用 extern 关键字将 globalVar 声明为外部链接,并在其中一个源文件中定义它:
// header.h
extern int globalVar; // 声明// file1.cpp
#include "header.h"
int globalVar = 10; // 定义// file2.cpp
#include "header.h"
// 不再定义 globalVar
这样,globalVar 只在一个地方定义,符合 ODR 规则。
8. 总结
- 声明 引入实体并将其与名称关联,但不一定分配内存。
- 定义 不仅引入实体,还为其分配内存并初始化。
- ODR(One Definition Rule) 规定程序中每个非内联函数或变量只能有一个定义,以确保实体的行为和状态是唯一且一致的。
- ODR 的例外情况 包括内联函数、
constexpr变量、模板和匿名命名空间中的实体。 - 违反 ODR 可能导致链接错误、未定义行为或性能问题。
通过理解声明与定义的区别以及 ODR 规则,你可以编写更健壮、更可维护的 C++ 代码,避免常见的定义冲突问题。
准则: 函数的定义与语句序列中的表达式
函数的定义与语句序列中的表达式
在C++中,函数是程序的基本构建块之一,它封装了一组执行特定任务的语句。函数的定义通常包括一个返回类型、函数名、参数列表和一个由花括号 {} 包围的语句序列。这个语句序列可以包含多种类型的语句,其中一些语句可能包含表达式,这些表达式用于指定程序要执行的具体计算。
1. 函数的基本结构
一个典型的函数定义如下:
返回类型 函数名(参数列表) {// 语句序列
}
- 返回类型:指函数执行完毕后返回的值的类型。如果函数不返回任何值,则使用
void。 - 函数名:标识函数的名称,用于调用该函数。
- 参数列表:列出传递给函数的参数及其类型。参数可以在函数体内使用。
- 语句序列:由一系列语句组成,这些语句定义了函数的行为。每个语句以分号
;结尾。
2. 语句序列
语句序列是函数体的核心部分,它包含了函数要执行的所有操作。C++ 中的语句可以分为以下几类:
- 表达式语句:由一个表达式后面跟一个分号
;组成。表达式可以是简单的赋值、函数调用、算术运算等。 - 复合语句(代码块):由一对花括号
{}包围的多个语句组成,通常用于定义新的作用域。 - 控制语句:如
if、for、while等,用于控制程序的执行流程。 - 声明语句:用于声明变量或函数。
- 返回语句:用于从函数中返回一个值,并结束函数的执行。
3. 表达式
表达式是C++中最基本的计算单元,它由操作数和运算符组成,表示一个值或操作。表达式可以出现在语句中,也可以作为更大表达式的一部分。常见的表达式类型包括:
-
赋值表达式:将一个值赋给一个变量。
int x = 10; // 赋值表达式 x = x + 5; // 复合赋值表达式 -
算术表达式:执行数学运算,如加法、减法、乘法、除法等。
int sum = a + b; // 加法表达式 double average = (a + b + c) / 3.0; // 平均值计算 -
逻辑表达式:用于布尔运算,如
&&(与)、||(或)、!(非)。if (x > 0 && y < 10) { /* ... */ } // 逻辑表达式 -
函数调用表达式:调用另一个函数并传递参数。
int result = add(a, b); // 调用函数 add -
条件表达式:也称为三元运算符,根据条件选择两个表达式中的一个。
int max = (a > b) ? a : b; // 条件表达式 -
逗号表达式:由多个表达式组成,按顺序求值,结果为最后一个表达式的值。
int x = (a++, b++, a + b); // 逗号表达式
4. 表达式在语句中的应用
表达式通常出现在语句中,用于执行具体的计算或操作。以下是一些常见的场景:
-
赋值语句:
int x = 5; // 赋值表达式 x = x + 1; // 增量表达式 -
控制语句中的表达式:
if (x > 0) { /* ... */ } // 条件表达式 for (int i = 0; i < 10; i++) { /* ... */ } // 初始化、条件和增量表达式 while (x != 0) { /* ... */ } // 条件表达式 -
函数调用中的表达式:
int result = add(a + 1, b * 2); // 表达式作为参数 -
返回语句中的表达式:
return x + y; // 返回表达式的值
5. 示例:计算矩形的面积和周长
下面是一个完整的函数定义示例,展示了如何在函数中使用语句序列和表达式来执行计算:
#include <iostream>// 定义一个函数,计算矩形的面积
double calculateArea(double width, double height) {// 表达式语句:计算面积double area = width * height;return area; // 返回表达式的值
}// 定义一个函数,计算矩形的周长
double calculatePerimeter(double width, double height) {// 表达式语句:计算周长double perimeter = 2 * (width + height);return perimeter; // 返回表达式的值
}int main() {double width = 5.0;double height = 3.0;// 调用函数并输出结果std::cout << "Rectangle with width: " << width << " and height: " << height << "\n";std::cout << "Area: " << calculateArea(width, height) << "\n"; // 函数调用表达式std::cout << "Perimeter: " << calculatePerimeter(width, height) << "\n"; // 函数调用表达式return 0;
}
在这个例子中:
calculateArea和calculatePerimeter是两个函数,它们分别计算矩形的面积和周长。- 函数体内包含多个表达式语句,用于执行具体的计算。
return语句用于返回计算结果,返回值是一个表达式。main函数中调用了这两个函数,并将结果输出到控制台。
6. 总结
- 函数的定义:包括返回类型、函数名、参数列表和语句序列。语句序列定义了函数的行为。
- 语句序列:由多个语句组成,可以是表达式语句、复合语句、控制语句等。
- 表达式:是C++中最基本的计算单元,用于执行具体的操作或计算。表达式可以出现在语句中,也可以作为更大表达式的一部分。
- 表达式的作用:表达式用于指定程序要执行的计算,如赋值、算术运算、逻辑运算、函数调用等。
通过理解函数的定义、语句序列和表达式的概念,你可以更好地编写结构清晰、逻辑严谨的C++代码。表达式是程序执行的核心,掌握它们的使用可以帮助你更高效地实现复杂的计算和逻辑控制。
准则: C++ 中的名称查找、作用域与链接详解
在C++中,名称查找、作用域和链接是理解程序如何解析和管理标识符(如变量、函数、类等)的关键概念。它们共同决定了程序中某个名称的含义及其可见性范围。下面将详细解释这些概念,并通过示例来说明它们的工作原理。
1. 名称查找(Name Lookup)
名称查找是指编译器在遇到一个名称时,如何找到该名称对应的声明。编译器会按照一定的规则搜索当前的作用域,以及外部作用域,直到找到匹配的声明。如果找不到匹配的声明,编译器会报错。
名称查找的规则:
- 从内到外:编译器首先在最内层的作用域中查找名称,如果找不到,则向外层作用域逐层查找,直到全局作用域。
- 作用域链:每个作用域可以嵌套在另一个作用域中,形成作用域链。编译器会沿着这条链进行查找。
- 命名空间:C++ 支持命名空间,不同命名空间中的同名标识符不会冲突。编译器会根据上下文确定应该使用哪个命名空间中的名称。
示例:
#include <iostream>namespace A {int x = 10; // 命名空间 A 中的 x
}namespace B {int x = 20; // 命名空间 B 中的 x
}int main() {int x = 30; // 局部变量 xstd::cout << "Local x: " << x << "\n"; // 输出局部变量 x (30)std::cout << "A::x: " << A::x << "\n"; // 输出命名空间 A 中的 x (10)std::cout << "B::x: " << B::x << "\n"; // 输出命名空间 B 中的 x (20)return 0;
}
在这个例子中,main 函数中有三个 x,分别是局部变量 x、命名空间 A 中的 x 和命名空间 B 中的 x。编译器根据名称查找规则,首先找到局部变量 x,然后根据显式的命名空间限定符找到 A::x 和 B::x。
2. 作用域(Scope)
作用域是指一个名称在程序中有效的区域。每个名称只能在其作用域内被访问。C++ 中有几种常见的作用域类型:
-
块作用域(Block Scope):在一对大括号
{}内定义的名称,只在该代码块内有效。例如,函数体、if语句、for循环等都是块作用域。 -
函数作用域(Function Scope):函数参数和函数内部定义的变量具有函数作用域,它们只能在该函数内部访问。
-
文件作用域(File Scope):在文件中定义但不在任何函数或块内的名称具有文件作用域,通常用于全局变量和全局函数。
-
命名空间作用域(Namespace Scope):在命名空间中定义的名称具有命名空间作用域,它们只能通过命名空间限定符访问。
-
类作用域(Class Scope):类中的成员变量和成员函数具有类作用域,它们可以通过对象或类名访问。
示例:
#include <iostream>int globalVar = 100; // 文件作用域void foo() {int localVar = 42; // 函数作用域if (true) {int blockVar = 50; // 块作用域std::cout << "Inside block: " << blockVar << "\n";}// blockVar 在这里不可见std::cout << "Inside function: " << localVar << "\n";
}class MyClass {
public:int memberVar = 20; // 类作用域void print() {std::cout << "Member variable: " << memberVar << "\n";}
};int main() {MyClass obj;obj.print(); // 访问类作用域中的成员变量std::cout << "Global variable: " << globalVar << "\n";foo(); // 调用函数return 0;
}
在这个例子中:
globalVar是全局变量,具有文件作用域,可以在整个文件中访问。localVar是函数foo中的局部变量,具有函数作用域,只能在foo内部访问。blockVar是if语句块中的局部变量,具有块作用域,只能在if语句块内访问。memberVar是类MyClass中的成员变量,具有类作用域,只能通过类的对象或类名访问。
3. 链接(Linkage)
链接是指多个翻译单元(即不同的源文件)之间的名称共享机制。C++ 中的名称可以有不同的链接方式,这决定了它们是否可以在不同的翻译单元中引用同一个实体。
链接的类型:
-
外部链接(External Linkage):具有外部链接的名称可以在多个翻译单元之间共享。全局变量、非静态成员函数和非内联函数通常具有外部链接。要使一个名称具有外部链接,可以在声明时使用
extern关键字(对于变量),或者直接在文件作用域中声明(对于函数)。 -
内部链接(Internal Linkage):具有内部链接的名称只能在当前翻译单元内访问。使用
static关键字可以将变量或函数的链接设置为内部链接。这样,即使其他文件中有相同名称的变量或函数,它们也不会冲突。 -
无链接(No Linkage):具有无链接的名称只能在当前作用域内访问。局部变量、匿名命名空间中的变量和类的静态成员函数通常具有无链接。
示例:
// file1.cpp
#include <iostream>int globalVar = 100; // 具有外部链接static int staticVar = 200; // 具有内部链接void externalFunc() {std::cout << "External function called\n";
}static void internalFunc() {std::cout << "Internal function called\n";
}// file2.cpp
#include <iostream>extern int globalVar; // 声明外部链接的全局变量void externalFunc(); // 声明外部链接的函数int main() {std::cout << "Global variable from file1: " << globalVar << "\n";externalFunc(); // 调用 file1 中的外部函数// staticVar 和 internalFunc 在这里不可见,因为它们具有内部链接return 0;
}
在这个例子中:
globalVar是一个具有外部链接的全局变量,可以在file1.cpp和file2.cpp中共享。staticVar是一个具有内部链接的静态变量,只能在file1.cpp中访问。externalFunc是一个具有外部链接的函数,可以在file1.cpp和file2.cpp中调用。internalFunc是一个具有内部链接的静态函数,只能在file1.cpp中调用。
4. 名称的作用域与链接的关系
-
全局变量:默认情况下,全局变量具有外部链接,可以在多个翻译单元中共享。如果希望限制其可见性,可以使用
static关键字将其链接设置为内部链接。 -
函数:非静态成员函数和非内联函数默认具有外部链接,可以在多个翻译单元中调用。静态成员函数和匿名命名空间中的函数具有内部链接,只能在当前文件中调用。
-
类成员:类的成员变量和成员函数具有类作用域,它们可以通过类的对象或类名访问。类的静态成员变量具有外部链接,可以在多个翻译单元中共享;而静态成员函数也具有外部链接,但它们不能访问非静态成员变量。
-
命名空间:命名空间中的名称具有命名空间作用域,它们可以通过命名空间限定符访问。不同命名空间中的同名标识符不会冲突。
总结
- 名称查找:编译器根据作用域链从内到外查找名称,确保每个名称都能正确关联到其声明。
- 作用域:每个名称仅在程序的某个部分(称为其作用域)内有效,超出作用域后无法访问。
- 链接:某些名称具有外部链接,可以在多个翻译单元中共享;而其他名称具有内部链接或无链接,只能在当前文件或作用域内访问。
通过理解这些概念,你可以更好地控制程序中名称的可见性和共享方式,避免命名冲突,并编写更模块化和可维护的代码。
准则:变量范畴
在C++中,变量是指那些声明的对象和引用,只要它们不是非静态数据成员。这个定义可以从几个方面来解释:
1. 对象与引用
- 对象:是具有类型和存储空间的实体,可以用来保存值。例如,当你声明一个
int类型的变量时,编译器会为它分配足够的内存来存储一个整数值。 - 引用:是对另一个对象的别名,它必须在声明时初始化,并且一旦初始化后就不能改变所引用的对象。
int x = 10; // x 是一个对象,也是一个变量
int& y = x; // y 是对 x 的引用,y 也是一个变量
2. 非静态数据成员
- 非静态数据成员:是指类或结构体中的普通成员变量。这些成员变量是类的每个实例的一部分,每个对象都有自己独立的一份副本。它们虽然也是对象或引用,但因为它们属于类的某个实例,所以不被认为是“变量”。
class MyClass {
public:int member; // 非静态数据成员,不是变量static int staticMember; // 静态数据成员,是变量
};int MyClass::staticMember = 0; // 静态成员的定义MyClass obj;
obj.member = 42; // 访问非静态数据成员
MyClass::staticMember = 100; // 访问静态数据成员
3. 为什么非静态数据成员不是变量
- 作用域和生命周期:非静态数据成员的作用域限于类的实例,它们的生命周期与该实例相同。而变量通常指的是具有独立作用域和生命周期的实体,可以在函数、代码块等范围内使用。
- 访问方式:非静态数据成员必须通过类的实例来访问,不能直接作为独立的变量使用。而变量可以直接在作用域内使用,不需要依赖某个特定的对象。
4. 变量的特性
- 作用域:变量的作用域决定了它在程序中的可见性和生命周期。局部变量的作用域限于定义它们的代码块(如函数或循环),而全局变量的作用域可以扩展到整个文件或多个文件。
- 生命周期:变量的生命周期决定了它们在内存中存在的时间。局部变量通常在代码块结束时被销毁,而全局变量在整个程序运行期间都存在。
- 存储类别:变量可以有不同的存储类别,如自动变量(
auto)、静态变量(static)、外部变量(extern)等,这影响了它们的生命周期和作用域。
5. 示例
#include <iostream>class MyClass {
public:int member; // 非静态数据成员,不是变量static int staticMember; // 静态数据成员,是变量
};int MyClass::staticMember = 0; // 静态成员的定义void example() {int localVar = 42; // 局部变量static int staticVar = 0; // 局部静态变量int* ptr = new int(10); // 动态分配的对象,是变量int& ref = localVar; // 引用,是变量std::cout << "localVar: " << localVar << "\n";std::cout << "staticVar: " << staticVar << "\n";std::cout << "*ptr: " << *ptr << "\n";std::cout << "ref: " << ref << "\n";delete ptr; // 清理动态分配的内存
}int globalVar = 100; // 全局变量int main() {MyClass obj;obj.member = 20; // 访问非静态数据成员std::cout << "obj.member: " << obj.member << "\n"; // 输出非静态数据成员std::cout << "MyClass::staticMember: " << MyClass::staticMember << "\n"; // 输出静态数据成员example(); // 调用函数,展示局部变量return 0;
}
总结
- 变量是指那些声明的对象和引用,只要它们不是非静态数据成员。它们具有独立的作用域和生命周期,可以在程序的不同部分中使用。
- 非静态数据成员是类的实例的一部分,每个对象都有自己独立的一份副本,因此它们不属于“变量”的范畴。
- 静态数据成员虽然是类的一部分,但它们是共享的,并且具有全局或文件范围的作用域,因此它们可以被视为变量。
通过理解这些概念,你可以更好地把握C++中变量的定义和使用,以及如何区分不同类型的实体。
准则 :类型关联
C++ 中的每个对象、引用、函数和表达式都与一个类型相关联,该类型可以是基本类型、复合类型或用户定义类型,完整类型或不完整类型等。
在C++中,每个对象、引用、函数和表达式都与一个类型相关联。类型定义了数据的格式、可以对数据执行的操作以及数据的存储方式。C++中的类型可以分为几大类:基本类型、复合类型、用户定义类型、完整类型和不完整类型。下面将详细解释这些概念。
1. 基本类型(Primitive Types)
基本类型是语言内置的最简单的数据类型,它们直接对应于硬件支持的数据格式。常见的基本类型包括:
-
整数类型:
bool:布尔类型,值为true或false。char:字符类型,通常表示单个字符。int:整数类型,表示有符号整数。short,long,long long:不同大小的整数类型。unsigned int,unsigned short,unsigned long,unsigned long long:无符号整数类型。
-
浮点类型:
float:单精度浮点数。double:双精度浮点数。long double:扩展精度浮点数。
-
空类型:
void:表示没有值,常用于函数返回类型或指针类型。
bool flag = true;
char ch = 'A';
int num = 42;
float pi = 3.14f;
2. 复合类型(Compound Types)
复合类型是由多个基本类型或其它复合类型组合而成的类型。常见的复合类型包括:
-
指针类型:
- 指向某个类型的指针,表示内存地址。
- 例如,
int* p是指向int类型的指针。
-
数组类型:
- 固定大小的连续内存区域,用于存储相同类型的多个元素。
- 例如,
int arr[5]是包含5个int的数组。
-
引用类型:
- 对另一个对象的别名,必须在声明时初始化。
- 例如,
int& ref = num;是对num的引用。
-
函数类型:
- 表示函数的签名,包括返回类型和参数类型。
- 例如,
int (*func)(int, int)是指向接受两个int参数并返回int的函数的指针。
-
容器类型(标准库提供的复合类型):
std::vector<int>:动态数组。std::string:字符串类。std::map<int, std::string>:键值对映射。
int* ptr = # // 指针
int arr[5] = {1, 2, 3, 4, 5}; // 数组
int& ref = num; // 引用
std::vector<int> vec = {1, 2, 3, 4, 5}; // 动态数组
3. 用户定义类型(User-Defined Types)
用户定义类型是由程序员自己定义的类型,通常用于封装复杂的数据结构或行为。常见的用户定义类型包括:
-
结构体(struct):
- 用于组合多个不同类型的数据成员。
- 例如,
struct Point { int x, y; };定义了一个包含两个整数成员的结构体。
-
类(class):
- 类似于结构体,但默认成员是私有的,并且可以包含成员函数。
- 例如,
class Rectangle { public: int width, height; };定义了一个包含宽度和高度的类。
-
枚举(enum):
- 用于定义一组命名的常量。
- 例如,
enum Color { Red, Green, Blue };定义了一组颜色常量。
-
联合体(union):
- 用于共享同一块内存的不同类型的变量。
- 例如,
union Data { int i; float f; char str[20]; };定义了一个联合体,所有成员共享同一块内存。
-
模板(template):
- 用于定义泛型类型或函数,可以在编译时生成特定类型的代码。
- 例如,
template<typename T> class Vector { /* ... */ };定义了一个模板类,可以用于任何类型。
struct Point {int x, y;
};class Rectangle {
public:int width, height;
};enum Color { Red, Green, Blue };union Data {int i;float f;char str[20];
};template<typename T>
class Vector {T* data;size_t size;
};
4. 完整类型(Complete Types)与不完整类型(Incomplete Types)
-
完整类型:
- 完整类型是指编译器已经知道其完整定义的类型,包括大小和布局。对于完整类型,编译器可以为其分配内存并进行各种操作。
- 例如,
int,double,std::string,struct Point等都是完整类型。
-
不完整类型:
- 不完整类型是指编译器只知道该类型的名称,但还不知道其完整的定义。对于不完整类型,编译器无法为其分配内存,但可以进行某些有限的操作,如声明指针或引用。
- 常见的不完整类型包括:
- 前向声明的类:例如,
class Node;只声明了类的存在,但没有定义其成员。 - 数组类型:如果数组的大小未知,如
int arr[],则它是不完整的。 - void:
void是一种特殊的不完整类型,表示没有值。
- 前向声明的类:例如,
// 前向声明
class Node;// 可以声明指针,但不能创建对象
Node* pNode;// 完整定义
class Node {
public:int value;Node* next;
};// 现在可以创建对象
Node node;
5. 类型的作用
-
类型检查:编译器根据类型进行严格的类型检查,确保程序中的操作是合法的。例如,你不能将
int赋值给double而不需要显式的类型转换。 -
内存管理:类型决定了对象在内存中的布局和大小。编译器根据类型为对象分配适当的内存空间。
-
操作符重载:用户定义类型可以通过重载操作符来定义特定的行为。例如,你可以为自定义的类重载
+操作符,使其支持加法操作。 -
多态性:通过虚函数和继承机制,C++ 支持运行时多态性,允许基类指针或引用指向派生类对象,并调用派生类的成员函数。
6. 类型推导
C++ 提供了一些机制来自动推导类型,简化代码编写:
-
auto:编译器根据初始化表达式的类型自动推导变量的类型。auto x = 42; // x 的类型是 int -
decltype:根据表达式的类型推导出一个新的类型。int a = 10; decltype(a) b = 20; // b 的类型是 int -
constexpr:用于声明编译时常量表达式,确保某些计算在编译时完成。constexpr int square(int x) { return x * x; } constexpr int result = square(5); // result 的值在编译时确定
总结
在C++中,每个对象、引用、函数和表达式都与一个类型相关联,这个类型可以是基本类型、复合类型或用户定义类型,并且可以是完整类型或不完整类型。类型不仅决定了数据的存储方式,还影响了编译器的类型检查、内存管理和程序的行为。理解这些类型的概念和分类有助于编写更安全、更高效的C++代码。
