命名空间(namespace)是C++中用于解决名称冲突问题的重要机制。
命名空间的作用:
避免命名冲突:命名空间提供了一种将全局作用域划分成更小的作用域的机制,用于避免不同的代码中可能发生的命名冲突问题;
组织代码:将相关的实体放到同一个命名空间;
版本控制:不同版本的代码放到不同的命名空间中;
下面引用当前流行的命名空间使用指导原则:
提倡在已命名的名称空间中定义变量,而不是直接定义外部全局变量或者静态全局变量。
如果开发了一个函数库或者类库,提倡将其放在一个命名空间中。
对于using 声明,首先将其作用域设置为局部而不是全局。
包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有#include预编译指令后。
命名空间的基本概念:
命名空间提供了一种将全局作用域划分为不同区域的方法,防止不同库之间的名称冲突。
111// 定义命名空间
2namespace MyNamespace {
3 int value = 100;
4 void function() {
5 // 函数实现
6 }
7
8 class MyClass {
9 // 类定义
10 };
11}
直接使用完全限定名称
71int main() {
2 // 使用作用域解析运算符::访问命名空间成员
3 int x = MyNamespace::value;
4 MyNamespace::function();
5 MyNamespace::MyClass obj;
6 return 0;
7}
使用using声明
91using MyNamespace::value; // 只引入特定成员
2using MyNamespace::function;
3
4int main() {
5 int x = value; // 直接使用,无需前缀
6 function(); // 直接使用,无需前缀
7 MyNamespace::MyClass obj; // 未引入的成员仍需前缀
8 return 0;
9}
使用using指令
81using namespace MyNamespace; // 引入整个命名空间
2
3int main() {
4 int x = value; // 直接使用
5 function(); // 直接使用
6 MyClass obj; // 直接使用
7 return 0;
8}
命名空间可以嵌套定义:
131namespace Outer {
2 int x = 10;
3
4 namespace Inner {
5 int y = 20;
6 }
7}
8
9int main() {
10 int a = Outer::x;
11 int b = Outer::Inner::y;
12 return 0;
13}
可以为长命名空间名称创建别名:
111namespace VeryLongNamespaceName {
2 void function() { }
3}
4
5// 创建别名
6namespace Short = VeryLongNamespaceName;
7
8int main() {
9 Short::function(); // 使用别名
10 return 0;
11}
匿名命名空间中的成员具有文件作用域,类似于static:
81namespace {
2 int hidden_value = 42; // 只在当前文件可见
3}
4
5int main() {
6 int x = hidden_value; // 可直接访问
7 return 0;
8}
当使用一个名称时,C++按以下顺序查找:
当前作用域
外层作用域
使用using声明引入的名称
使用using指令引入的命名空间
全局作用域
命名空间的跨模块调用:
命名空间中的实体跨模块调用时,要在新的源文件中再次定义同名的命名空间,在其中通过extern引入实体。
进行联合编译时,这两次定义被认为是同一个命名空间。
const关键字是C++中非常重要的一个关键字,它用于声明常量,保证数据不被修改。
定义常量
31const int MAX_SIZE = 100; // 定义整型常量
2const double PI = 3.14159; // 定义浮点型常量
3const char* MESSAGE = "Hello"; // 定义字符串常量
与指针结合使用
xxxxxxxxxx
141// 指向常量的指针(不能通过指针修改所指向的内容)
2const int* p1 = &value; // 或 int const* p1 = &value;
3// *p1 = 20; // 错误!不能修改所指向的内容
4p1 = &other; // 正确,可以改变指针指向
5
6// 常量指针(指针本身是常量,不能改变指向)
7int* const p2 = &value;
8*p2 = 20; // 正确,可以修改所指向的内容
9// p2 = &other; // 错误!不能改变指针指向
10
11// 指向常量的常量指针(既不能修改指针,也不能修改所指内容)
12const int* const p3 = &value;
13// *p3 = 20; // 错误!
14// p3 = &other; // 错误!
常量参数
xxxxxxxxxx
111void display(const int value) {
2 // value = 100; // 错误!不能修改常量参数
3 cout << value << endl;
4}
5
6void processArray(const int* arr, int size) {
7 // arr[0] = 100; // 错误!不能修改数组元素
8 for (int i = 0; i < size; i++) {
9 cout << arr[i] << " ";
10 }
11}
常量引用
xxxxxxxxxx
51void processLargeObject(const BigObject& obj) {
2 // 使用常量引用可以避免对象拷贝,同时防止修改
3 // obj.modify(); // 错误!不能修改常量引用
4 obj.display(); // 正确,可以调用不修改对象的方法
5}
常量成员变量
xxxxxxxxxx
121class Circle {
2private:
3 const double PI = 3.14159; // 常量成员变量
4 double radius;
5
6public:
7 Circle(double r) : radius(r) {}
8
9 double getArea() const {
10 return PI * radius * radius;
11 }
12};
常量成员函数
xxxxxxxxxx
191class Student {
2private:
3 string name;
4 int age;
5
6public:
7 Student(string n, int a) : name(n), age(a) {}
8
9 // 常量成员函数,不能修改类的成员变量
10 string getName() const {
11 // age++; // 错误!常量成员函数不能修改成员变量
12 return name;
13 }
14
15 // 非常量成员函数,可以修改类的成员变量
16 void setAge(int a) {
17 age = a; // 正确
18 }
19};
常量对象
xxxxxxxxxx
31const Student student("张三", 20);
2cout << student.getName(); // 正确,可以调用常量成员函数
3// student.setAge(21); // 错误!常量对象不能调用非常量成员函数
常量表达式(C++11)
xxxxxxxxxx
51constexpr int factorial(int n) {
2 return n <= 1 ? 1 : (n * factorial(n-1));
3}
4
5constexpr int result = factorial(5); // 编译时计算
成员函数重载
xxxxxxxxxx
151class MyString {
2private:
3 char* data;
4
5public:
6 // 非常量版本,返回可修改的引用
7 char& operator[](size_t index) {
8 return data[index];
9 }
10
11 // 常量版本,返回常量引用
12 const char& operator[](size_t index) const {
13 return data[index];
14 }
15};
增强代码安全性:防止意外修改数据
提高代码可读性:明确表明哪些数据不应被修改
编译器优化:编译器可以对常量进行更多优化
接口设计:明确函数是否会修改传入的参数
const常量和宏定义常量(#define)是C++中两种定义常量的方式,它们有以下几个重要区别:
1.类型检查
const常量:有类型检查,是强类型的
xxxxxxxxxx
11const int MAX_SIZE = 100; // 明确指定为int类型
宏定义:无类型检查,只是简单的文本替换
xxxxxxxxxx
11// 没有类型信息
2.内存分配
const常量:会分配内存,是变量
xxxxxxxxxx
11const int MAX_SIZE = 100; // 在内存中有存储位置
宏定义:不分配内存,预处理阶段进行文本替换
xxxxxxxxxx
11// 预处理时直接替换为100
3.作用域
const常量:遵循作用域规则
xxxxxxxxxx
41void func() {
2 const int LOCAL_MAX = 10; // 局部作用域
3}
4// LOCAL_MAX在这里不可见
宏定义:从定义处到文件结束或#undef处都有效
xxxxxxxxxx
21// 从这里开始有效
2// 在整个文件中都可见,除非被#undef
4.调试
const常量:可以在调试器中查看
宏定义:在预处理阶段被替换,调试时看不到原始定义
5.安全性
const常量:更安全,可以避免一些意外错误
xxxxxxxxxx
11const char* str = "Hello"; // 指向常量字符串的指针
宏定义:可能导致意外问题
xxxxxxxxxx
21
2int result = SQUARE(2+3); // 展开为2+3*2+3,结果为11而非25
6.指针操作
const常量:可以获取地址
xxxxxxxxxx
21const int MAX = 100;
2const int* p = &MAX; // 可以取地址
宏定义:无法获取地址
xxxxxxxxxx
21
2// int* p = &MAX; // 错误,相当于 int* p = &100;
7.复杂常量定义
const常量:可以定义复杂类型的常量
xxxxxxxxxx
11const struct Point { int x; int y; } origin = {0, 0};
宏定义:难以定义复杂类型
xxxxxxxxxx
11// 使用时需要指定类型
8.函数内联
const常量:不会导致代码膨胀
宏定义:可能导致代码膨胀
在现代C++中,推荐使用const常量而非宏定义:
使用const定义常量值
使用constexpr定义编译期常量
使用inline函数替代宏函数
仅在特殊情况下使用宏定义(如条件编译)
new和delete是C++中用于动态内存管理的操作符,它们允许程序在运行时分配和释放内存。
基本语法
xxxxxxxxxx
11pointer_variable = new data_type;
分配单个对象
xxxxxxxxxx
61int *p = new int; // 分配一个int类型的内存空间
2*p = 10; // 赋值
3delete p; // 释放内存
4
5int *q = new int(20); // 分配并初始化
6delete q;
分配数组
xxxxxxxxxx
51int *arr = new int[5]; // 分配包含5个int的数组
2arr[0] = 10;
3arr[1] = 20;
4// ...
5delete[] arr; // 释放数组内存
分配对象
xxxxxxxxxx
81class MyClass {
2public:
3 MyClass() { cout << "构造函数调用" << endl; }
4 ~MyClass() { cout << "析构函数调用" << endl; }
5};
6
7MyClass *obj = new MyClass(); // 调用构造函数
8delete obj; // 调用析构函数并释放内存
基本语法
xxxxxxxxxx
21delete pointer_variable; // 释放单个对象
2delete[] array_pointer; // 释放数组
注意事项
释放数组必须使用delete[]
,否则只会调用第一个元素的析构函数
不能对同一内存空间delete两次(未定义行为)
可以对nullptr执行delete操作(无效果但安全)
new的执行过程
调用operator new分配内存
在分配的内存上调用构造函数
返回对象的指针
delete的执行过程
调用对象的析构函数
调用operator delete释放内存
异常处理
xxxxxxxxxx
71try {
2 int *huge_array = new int[10000000000]; // 尝试分配大量内存
3 // 使用内存
4 delete[] huge_array;
5} catch (const std::bad_alloc& e) {
6 cout << "内存分配失败: " << e.what() << endl;
7}
nothrow形式
xxxxxxxxxx
41int *p = new(nothrow) int[1000000000];
2if (p == nullptr) {
3 cout << "内存分配失败" << endl;
4}
重载全局new/delete
xxxxxxxxxx
91void* operator new(size_t size) {
2 cout << "自定义new: 分配 " << size << " 字节" << endl;
3 return malloc(size);
4}
5
6void operator delete(void* ptr) {
7 cout << "自定义delete: 释放内存" << endl;
8 free(ptr);
9}
重载类的new/delete
xxxxxxxxxx
121class MyClass {
2public:
3 void* operator new(size_t size) {
4 cout << "MyClass::new: " << size << " 字节" << endl;
5 return ::operator new(size);
6 }
7
8 void operator delete(void* ptr) {
9 cout << "MyClass::delete" << endl;
10 ::operator delete(ptr);
11 }
12};
内存泄漏
xxxxxxxxxx
41void memoryLeak() {
2 int *p = new int; // 分配内存
3 // 没有对应的delete语句,导致内存泄漏
4}
悬挂指针
xxxxxxxxxx
51int *p = new int(10);
2int *q = p; // q指向同一块内存
3delete p; // 释放内存
4p = nullptr; // p置为nullptr
5// *q = 20; // 危险!q成为悬挂指针,访问已释放的内存
###
避免出现问题,使用智能指针:
xxxxxxxxxx
101
2
3// 使用unique_ptr
4std::unique_ptr<int> p1(new int(10));
5// 自动释放,无需手动delete
6
7// 使用shared_ptr
8std::shared_ptr<int> p2 = std::make_shared<int>(20);
9std::shared_ptr<int> p3 = p2; // 引用计数增加
10// 当所有shared_ptr都销毁时,内存自动释放
new/delete会调用构造函数和析构函数,malloc/free不会
new返回正确类型的指针,malloc返回void*
new/delete可以被重载,malloc/free不能
内存分配失败时,new抛出异常,malloc返回NULL
malloc/free是库函数,new/delete是表达式,后两者使用时不是函数的写法
malloc申请的空间不会进行初始化,获取到的空间是有脏数据的,但new表达式申请空间时可以直接初始化
malloc的参数是字节数,new表达式不需要传递字节数,会根据相应类型自动获取空间大小
引用是已定义变量的别名,必须在定义时初始化,初始化后不能改变引用的对象。
xxxxxxxxxx
21int a = 10;
2int& ref = a; // ref是a的引用,ref和a指向同一块内存
必须初始化
xxxxxxxxxx
31int& ref; // 错误!引用必须初始化
2int a = 10;
3int& ref = a; // 正确
初始化后不能改变
xxxxxxxxxx
41int a = 10;
2int b = 20;
3int& ref = a; // ref引用a
4ref = b; // 这不是让ref引用b,而是将b的值赋给a
没有空引用,引用必须指向一个有效的对象,不存在空引用的概念。
没有引用的引用,C++不支持引用的引用。
没有指向引用的指针,不能创建指向引用的指针。
函数参数
xxxxxxxxxx
131// 传值
2void swap1(int a, int b) {
3 int temp = a;
4 a = b;
5 b = temp;
6} // 不会改变原始值
7
8// 传引用
9void swap2(int& a, int& b) {
10 int temp = a;
11 a = b;
12 b = temp;
13} // 会改变原始值
函数返回值
xxxxxxxxxx
91int& getElement(vector<int>& vec, int index) {
2 return vec[index]; // 返回引用
3}
4
5int main() {
6 vector<int> numbers = {10, 20, 30};
7 getElement(numbers, 1) = 50; // 修改vector中的元素
8 // numbers现在是 {10, 50, 30}
9}
常量引用
xxxxxxxxxx
41void printValue(const int& value) {
2 // value = 100; // 错误!不能修改常量引用
3 cout << value << endl;
4}
必须初始化:引用必须在定义时初始化,指针可以稍后初始化
不能改变引用对象:引用一旦初始化,不能改变引用的对象;指针可以随时改变指向
没有空引用:引用必须指向有效对象;指针可以为nullptr
使用方式:引用使用时与普通变量相同;指针需要解引用操作
内存占用:引用不占用额外内存;指针是一个变量,占用内存
xxxxxxxxxx
91// 指针
2int a = 10;
3int* ptr = &a;
4*ptr = 20; // 通过指针修改a的值
5
6// 引用
7int b = 30;
8int& ref = b;
9ref = 40; // 直接修改b的值
C++11引入了右值引用,用于支持移动语义和完美转发。
xxxxxxxxxx
21// 右值引用
2int&& rref = 10; // 绑定到右值
移动语义
xxxxxxxxxx
81class MyString {
2public:
3 // 移动构造函数
4 MyString(MyString&& other) noexcept {
5 data = other.data;
6 other.data = nullptr; // 资源转移
7 }
8};
完美转发
xxxxxxxxxx
41template<typename T, typename Arg>
2void wrapper(Arg&& arg) {
3 T object(std::forward<Arg>(arg)); // 完美转发参数
4}
不要返回局部变量的引用
xxxxxxxxxx
41int& getLocalRef() {
2 int local = 10;
3 return local; // 错误!返回局部变量的引用
4}
引用作为类成员
xxxxxxxxxx
61class MyClass {
2private:
3 int& ref; // 引用成员
4public:
5 MyClass(int& r) : ref(r) {} // 必须在初始化列表中初始化
6};
引用的本质:引用在底层实现上通常是通过指针实现的,但在语法层面上提供了更安全、更方便的使用方式。
C++提供了多种类型转换机制,比传统C语言的类型转换更加安全和明确。
static_cast
是最常用的类型转换操作符,主要用于:
基本数据类型之间的转换
有继承关系的类指针或引用之间的转换(向上转换安全,向下转换不检查)
任何类型转换为void类型
xxxxxxxxxx
51double d = 3.14159;
2int i = static_cast<int>(d); // 将double转换为int
3
4Base* basePtr = new Derived();
5Derived* derivedPtr = static_cast<Derived*>(basePtr); // 向下转换,不安全
dynamic_cast
主要用于有继承关系的类指针或引用之间的转换,特点是:
在运行时进行类型检查
只能用于含有虚函数的类
向下转换时,如果转换失败,对指针返回nullptr,对引用抛出异常
xxxxxxxxxx
81Base* basePtr = new Derived();
2Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 安全的向下转换
3
4if (derivedPtr) {
5 // 转换成功
6} else {
7 // 转换失败
8}
const_cast
用于移除或添加const/volatile限定符:
可以将const对象转换为非const对象
不能用于改变对象的类型
通常用于处理API不一致的情况
xxxxxxxxxx
31const int a = 10;
2int* ptr = const_cast<int*>(&a); // 移除const限定
3*ptr = 20; // 危险操作!可能导致未定义行为
reinterpret_cast
是最危险的转换操作符:
可以在任意指针类型之间转换
可以在指针和整数类型之间转换
不检查类型安全性,完全依赖程序员保证正确性
xxxxxxxxxx
31int* p = new int(65);
2char* ch = reinterpret_cast<char*>(p); // 将int*转换为char*
3std::cout << *ch; // 可能输出'A'(ASCII 65)
C++仍然支持C风格的类型转换,但不推荐使用:
xxxxxxxxxx
31double d = 3.14159;
2int i = (int)d; // C风格转换
3int j = int(d); // 函数式转换
转换类型 | 安全性 | 运行时检查 | 主要用途 |
---|---|---|---|
static_cast | 中等 | 否 | 基本类型转换,有继承关系的类转换 |
dynamic_cast | 高 | 是 | 有继承关系的类转换,运行时类型识别 |
const_cast | 低 | 否 | 移除或添加const/volatile限定符 |
reinterpret_cast | 极低 | 否 | 任意指针类型之间的转换 |
C风格转换 | 低 | 否 | 不推荐使用 |
优先使用C++风格的类型转换而非C风格转换
尽量避免使用reinterpret_cast
和const_cast
对于类层次结构中的向下转换,优先使用dynamic_cast
对于简单的数值类型转换,使用static_cast
编写代码时尽量减少类型转换的需求
函数重载是C++中一个重要的特性,它允许在同一作用域中定义多个同名函数,只要它们的参数列表不同。
函数重载允许同一个函数名有多个不同的实现,根据调用时提供的参数类型和数量来决定调用哪个函数。
xxxxxxxxxx
31void print(int num);
2void print(double num);
3void print(const char* text);
必须有不同的参数列表
函数重载必须在以下至少一个方面有所不同:
参数数量不同
参数类型不同
参数顺序不同
xxxxxxxxxx
51void func(int a); // 参数类型不同
2void func(double a);
3void func(int a, int b); // 参数数量不同
4void func(int a, double b); // 参数类型不同
5void func(double a, int b); // 参数顺序不同
仅返回类型不同不构成重载
xxxxxxxxxx
21int getValue();
2double getValue(); // 错误!仅返回类型不同不构成重载
const修饰符可以构成重载
xxxxxxxxxx
21void display(int* ptr);
2void display(const int* ptr); // 参数类型不同,构成重载
对于成员函数,const修饰符也可以构成重载:
xxxxxxxxxx
51class MyClass {
2public:
3 void func(); // 非const成员函数
4 void func() const; // const成员函数,构成重载
5};
编译器如何选择调用哪个重载函数:
精确匹配:寻找参数类型完全匹配的函数
提升转换:如char到int,float到double的转换
标准转换:如int到double,派生类到基类的转换
用户定义的转换:通过转换构造函数或转换运算符
可变参数函数:如果前面都没匹配到
标准库中的重载
xxxxxxxxxx
31// 标准库中的min函数重载
2int min(int a, int b);
3double min(double a, double b);
构造函数重载
xxxxxxxxxx
71class String {
2public:
3 String(); // 默认构造函数
4 String(const char* str); // C风格字符串构造
5 String(const String& other); // 拷贝构造函数
6 String(String&& other); // 移动构造函数(C++11)
7};
运算符重载
xxxxxxxxxx
51class Complex {
2public:
3 Complex operator+(const Complex& other);
4 Complex operator+(double real);
5};
默认参数与重载
默认参数可能导致调用歧义:
xxxxxxxxxx
41void show(int a = 10);
2void show(int a, int b = 20);
3
4show(5); // 歧义!两个函数都可以匹配
函数重载与类型转换
当多个重载函数都可以通过类型转换匹配时,可能导致歧义:
xxxxxxxxxx
41void process(int value);
2void process(double value);
3
4process('a'); // 'a'可以转换为int或double,但int是更好的匹配
重载与作用域
重载函数必须在同一作用域内:
xxxxxxxxxx
51void display(int);
2
3namespace A {
4 void display(double); // 不构成重载,在不同作用域
5}
提高代码可读性:使用相同的函数名表示相似的操作
简化接口:不需要为相似功能创建不同的函数名
类型安全:编译器会选择最匹配的函数
便于维护:功能相似的函数集中在一起
函数重载和函数模板可以结合使用:
xxxxxxxxxx
71// 具体函数
2void print(int value);
3void print(const char* text);
4
5// 函数模板
6template<typename T>
7void print(const T& value);
当调用print
函数时,编译器会优先选择具体函数,如果没有匹配的具体函数,才会实例化模板。
默认参数是指在函数声明时为形参提供的默认值,当函数调用时如果没有提供该参数的值,则使用默认值。
xxxxxxxxxx
11void printMessage(const char* message, int times = 1);
在这个例子中,times
参数有默认值1,调用时可以省略该参数。
1.默认参数必须从右向左提供
xxxxxxxxxx
51// 正确
2void func(int a, int b = 5, int c = 10);
3
4// 错误
5void func(int a = 1, int b, int c);
2.默认参数只能在声明或定义中指定一次
xxxxxxxxxx
71// 在声明中指定
2void func(int a, int b = 5);
3
4// 在定义中不能再次指定
5void func(int a, int b) {
6 // 函数体
7}
3.默认参数可以是常量、全局变量或函数调用
xxxxxxxxxx
61const int DEFAULT_SIZE = 100;
2int globalVar = 200;
3
4void resize(int size = DEFAULT_SIZE);
5void process(int value = getValue());
6void configure(int setting = globalVar);
使用默认参数的函数可以通过以下方式调用:
xxxxxxxxxx
51void display(int a, int b = 5, int c = 10);
2
3display(1); // 等价于 display(1, 5, 10)
4display(1, 2); // 等价于 display(1, 2, 10)
5display(1, 2, 3); // 所有参数都显式提供
默认参数和函数重载可能导致调用歧义:
xxxxxxxxxx
41void print(int a);
2void print(int a, int b = 10);
3
4print(5); // 歧义!两个函数都可以匹配
在类中,成员函数也可以使用默认参数:
xxxxxxxxxx
81class Rectangle {
2public:
3 void setDimensions(int length, int width = 10);
4 int getArea() const;
5private:
6 int length;
7 int width;
8};
默认参数在函数调用时计算,而不是在函数定义时:
xxxxxxxxxx
91int counter = 0;
2void func(int value = counter++);
3
4int main() {
5 counter = 5;
6 func(); // value = 5,调用后counter = 6
7 func(); // value = 6,调用后counter = 7
8 return 0;
9}
优点:
提高代码可读性和灵活性
减少函数重载的需要
使API更易于使用
缺点:
可能导致函数调用歧义
修改默认值可能影响现有代码
在大型项目中可能难以追踪默认值的来源
bool类型是C++中表示布尔值的基本数据类型,用于表示逻辑值:真(true)或假(false)。
bool类型在C++98标准中被引入,是C++的基本数据类型之一。
xxxxxxxxxx
21bool flag = true; // 真
2bool test = false; // 假
bool类型在大多数实现中占用1个字节(8位)的内存空间
虽然理论上只需要1位就能表示true/false,但为了内存对齐和访问效率,通常使用1字节
xxxxxxxxxx
81
2using namespace std;
3
4int main() {
5 cout << "bool类型的大小: " << sizeof(bool) << " 字节" << endl;
6 return 0;
7}
8// 输出: bool类型的大小: 1 字节
1.其他类型转换为bool
数值类型转换为bool:0转换为false,非0转换为true
指针类型转换为bool:nullptr转换为false,非空指针转换为true
xxxxxxxxxx
71bool b1 = 42; // true
2bool b2 = 0; // false
3bool b3 = -3.14; // true
4bool b4 = nullptr; // false
5
6int* p = new int(5);
7bool b5 = p; // true
2.bool转换为其他类型
bool转换为整型:true转换为1,false转换为0
bool转换为浮点型:true转换为1.0,false转换为0.0
xxxxxxxxxx
61bool flag = true;
2int i = flag; // i = 1
3double d = flag; // d = 1.0
4
5bool test = false;
6int j = test; // j = 0
bool类型最常用于条件语句中:
xxxxxxxxxx
131bool isValid = true;
2
3if (isValid) {
4 // 条件为真时执行
5}
6
7while (isValid) {
8 // 条件为真时循环执行
9}
10
11// 条件表达式
12int age = 20;
13bool isAdult = (age >= 18); // true
bool类型可以进行逻辑运算:
xxxxxxxxxx
61bool a = true;
2bool b = false;
3
4bool result1 = a && b; // 逻辑与: false
5bool result2 = a || b; // 逻辑或: true
6bool result3 = !a; // 逻辑非: false
C语言没有内置的bool类型,通常使用整型(0表示假,非0表示真)
C99标准引入了_Bool
类型和<stdbool.h>
头文件,提供了bool、true、false的宏定义
C++的bool是原生类型,不需要额外的头文件
inline函数(内联函数)是C++中一种特殊的函数类型,它的主要目的是通过在调用点直接插入函数代码来避免函数调用的开销。
内联函数是一种对编译器的请求,希望编译器将函数调用替换为函数体代码,从而避免函数调用的开销。
xxxxxxxxxx
81inline int max(int a, int b) {
2 return (a > b) ? a : b;
3}
4
5int main() {
6 int result = max(10, 20); // 编译器可能会将这里替换为 int result = (10 > 20) ? 10 : 20;
7 return 0;
8}
1.性能优化
避免了函数调用的开销(压栈、跳转、返回等)
对于短小的函数特别有效
可能增加代码体积(代码膨胀)
2.只是对编译器的建议
inline
关键字只是对编译器的建议,不是强制命令
编译器可能会忽略内联请求,尤其是对于复杂函数
现代编译器有自己的内联启发式算法,可能会自动内联未标记为inline的函数
3.定义通常放在头文件中
内联函数的定义通常需要放在头文件中
这是因为编译器需要在每个调用点看到完整的函数定义
适合内联的函数
简短的函数(几行代码)
频繁调用的函数
访问器和修改器(getter/setter)
小型工具函数
xxxxxxxxxx
51inline void swap(int& a, int& b) {
2 int temp = a;
3 a = b;
4 b = temp;
5}
不适合内联的函数
包含循环或递归的函数
大型复杂函数
很少被调用的函数
包含大量局部变量的函数
类定义中直接定义的成员函数会被隐式地视为内联函数:
xxxxxxxxxx
161class Point {
2private:
3 int x, y;
4public:
5 // 隐式内联
6 int getX() { return x; }
7 int getY() { return y; }
8
9 // 显式内联
10 inline void setX(int newX);
11};
12
13// 类外定义的成员函数需要显式声明为inline
14inline void Point::setX(int newX) {
15 x = newX;
16}
内联函数相比宏定义有以下优势:
类型安全
可以进行参数检查
可以使用局部变量
可以进行调试
遵循作用域规则
xxxxxxxxxx
71// 宏定义(不推荐)
2
3
4// 内联函数(推荐)
5inline int square(int x) {
6 return x * x;
7}
特性 | 内联函数 (inline ) | 宏 (#define ) |
---|---|---|
类型安全 | 提供类型安全,编译器进行类型检查 | 没有类型检查,可能产生不匹配的错误 |
编译期替换 | 编译器决定是否内联(有优化机制) | 预处理器简单文本替换 |
代码可读性和调试性 | 支持断点调试,可读性和普通函数相似 | 调试困难,无法跟踪宏的展开过程 |
副作用 | 参数只求值一次,不会有多次求值副作用 | 参数会多次求值,可能导致副作用 |
代码膨胀 | 函数被多次内联可能导致代码膨胀 | 频繁替换也会导致代码膨胀 |
灵活性 | 适用于明确类型的函数 | 可以处理不同类型的参数 |
性能 | 小型函数可以避免函数调用开销 | 无函数调用开销 |
内联函数的限制
不能包含复杂的控制流(如多重循环)
不能是递归函数
不能有过多的局部变量
不能使用静态变量(会影响内联决策)
内联与链接
内联函数通常具有内部链接
多个翻译单元中的同名内联函数必须完全相同
过度使用的问题
代码膨胀
指令缓存效率降低
编译时间增加
异常处理是C++中处理程序运行时错误的一种机制,它允许程序在遇到错误时以结构化的方式响应。
异常处理包含三个主要部分:
try块:包含可能引发异常的代码
throw语句:用于抛出异常
catch块:用于捕获并处理异常
xxxxxxxxxx
91try {
2 // 可能引发异常的代码
3 if (某个错误条件) {
4 throw 异常对象; // 抛出异常
5 }
6}
7catch (异常类型 变量名) {
8 // 处理异常的代码
9}
抛出异常
可以抛出任何类型的异常:
xxxxxxxxxx
31throw 100; // 抛出整数
2throw "除数不能为零"; // 抛出字符串
3throw std::runtime_error("内存分配失败"); // 抛出标准异常对象
捕获异常
使用catch块捕获异常:
xxxxxxxxxx
171try {
2 // 可能抛出异常的代码
3}
4catch (int e) {
5 // 处理整数类型的异常
6}
7catch (const char* e) {
8 // 处理字符串类型的异常
9}
10catch (const std::exception& e) {
11 // 处理标准异常
12 std::cout << "标准异常: " << e.what() << std::endl;
13}
14catch (...) {
15 // 捕获任何类型的异常
16 std::cout << "捕获到未知异常" << std::endl;
17}
C++标准库提供了一系列异常类,都派生自std::exception
基类:
xxxxxxxxxx
31// 标准异常类
2// bad_alloc异常
3// bad_cast异常
常用的标准异常类:
std::exception
:所有标准异常的基类
std::runtime_error
:运行时错误
std::logic_error
:逻辑错误
std::out_of_range
:超出范围错误
std::invalid_argument
:无效参数
std::bad_alloc
:内存分配失败
std::bad_cast
:错误的类型转换
可以通过继承std::exception
创建自定义异常类:
xxxxxxxxxx
191class MyException : public std::exception {
2private:
3 std::string message;
4
5public:
6 MyException(const std::string& msg) : message(msg) {}
7
8 virtual const char* what() const noexcept override {
9 return message.c_str();
10 }
11};
12
13// 使用自定义异常
14try {
15 throw MyException("这是一个自定义异常");
16}
17catch (const MyException& e) {
18 std::cout << e.what() << std::endl;
19}
C++11之前可以使用异常规范指定函数可能抛出的异常类型:
xxxxxxxxxx
11void func() throw(std::out_of_range, std::runtime_error); // C++11前
C++11引入了noexcept
说明符,表示函数不会抛出异常:
xxxxxxxxxx
21void safeFunction() noexcept; // 保证不抛出异常
2void mayThrow() noexcept(false); // 可能抛出异常
xxxxxxxxxx
361
2
3
4
5double divide(double a, double b) {
6 if (b == 0) {
7 throw std::invalid_argument("除数不能为零");
8 }
9 return a / b;
10}
11
12int main() {
13 try {
14 std::vector<int> vec;
15 vec.at(10) = 100; // 会抛出std::out_of_range异常
16 }
17 catch (const std::out_of_range& e) {
18 std::cout << "越界访问: " << e.what() << std::endl;
19 }
20
21 try {
22 double result = divide(10, 0);
23 std::cout << "结果: " << result << std::endl;
24 }
25 catch (const std::invalid_argument& e) {
26 std::cout << "参数错误: " << e.what() << std::endl;
27 }
28 catch (const std::exception& e) {
29 std::cout << "其他标准异常: " << e.what() << std::endl;
30 }
31 catch (...) {
32 std::cout << "未知异常" << std::endl;
33 }
34
35 return 0;
36}
C++程序的内存空间通常分为以下几个区域:
栈区:操作系统控制,由高地址向低地址生长,编译器做了优化,显示地址时栈区和其他区域保持一致的方向。
堆区:程序员分配,由低地址向高地址生长,堆区与栈区没有明确的界限。
全局/静态区:读写段(数据段),存放全局变量、静态变量。
文字常量区:只读段,存放程序中直接使用的常量,如const char * p = "hello"; hello这个内容就存在文字常量区。
程序代码区:只读段,存放函数体的二进制代码。
Note
因为编译器的优化, 局部变量的地址分配通常是:
后创建的变量分配高地址
先创建的变量分配低地址
C++支持两种主要的字符串表示方式:C风格字符串和C++的std::string
类。
C风格字符串是从C语言继承而来的字符串表示方式,它本质上是一个字符数组,以空字符'\0'
结尾。
xxxxxxxxxx
11char str[] = "Hello"; // 等价于 char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};
这里的str
数组实际包含6个字符,最后一个是不可见的空字符'\0'
,用于标记字符串的结束。
字符数组
xxxxxxxxxx
41char str1[6] = "Hello"; // 显式指定大小
2char str2[] = "Hello"; // 编译器自动计算大小
3char str3[10] = "Hello"; // 剩余位置填充'\0'
4char str4[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 显式包含结束符
字符指针
xxxxxxxxxx
31const char* str1 = "Hello"; // 指向字符串常量(只读)
2char* str2 = new char[6]; // 动态分配内存
3strcpy(str2, "Hello"); // 复制字符串到分配的内存
C风格字符串的操作主要通过<cstring>
(C++中)或<string.h>
(C中)头文件中的函数实现。
字符串长度
xxxxxxxxxx
21char str[] = "Hello";
2size_t length = strlen(str); // 返回5,不包括结束符'\0'
字符串复制
xxxxxxxxxx
41char source[] = "Hello";
2char destination[10];
3strcpy(destination, source); // 复制source到destination
4strncpy(destination, source, 3); // 复制最多3个字符
字符串连接
xxxxxxxxxx
41char str1[20] = "Hello";
2char str2[] = " World";
3strcat(str1, str2); // str1变为"Hello World"
4strncat(str1, str2, 3); // 连接最多3个字符
字符串比较
xxxxxxxxxx
31char str1[] = "apple";
2char str2[] = "banana";
3int result = strcmp(str1, str2); // 返回负值,因为str1按字典序小于str2
字符串查找
xxxxxxxxxx
31char str[] = "Hello World";
2char* ptr = strchr(str, 'o'); // 查找第一个'o',返回指向该位置的指针
3char* ptr2 = strstr(str, "World"); // 查找子串,返回指向子串开始位置的指针
栈上分配
xxxxxxxxxx
11char buffer[100]; // 在栈上分配固定大小的数组
堆上分配
xxxxxxxxxx
31char* dynamicStr = new char[100]; // 在堆上分配内存
2// 使用dynamicStr
3delete[] dynamicStr; // 使用完毕后释放内存
缓冲区溢出
xxxxxxxxxx
21char small[5];
2strcpy(small, "This is too long"); // 危险!会导致缓冲区溢出
忘记结束符
xxxxxxxxxx
21char str[5];
2str[0] = 'H'; str[1] = 'i'; // 没有添加'\0',不是有效的C风格字符串
字符串常量修改
xxxxxxxxxx
21const char* str = "Hello";
2// str[0] = 'h'; // 错误!尝试修改字符串常量
C风格字符串转std::string
xxxxxxxxxx
21const char* cStr = "Hello";
2std::string cppStr(cStr); // 构造std::string
std::string转C风格字符串
21std::string cppStr = "Hello";
2const char* cStr = cppStr.c_str(); // 获取C风格字符串(只读)