基础语法
基础语法
一、关键字
1. inline
作用: 使用inline修饰的函数称为内联函数,编译器会尝试对该函数进行内联优化,在保证函数特性的同时,避免函数调用开销,提高了程序执行效率。
原理: 函数的频繁调用存在开销(栈操作、参数传递、返回值传递、指令指针保存与恢复、上下文切换等),内联就是编译器用已编译好的二进制代码替换对该函数的调用指令。内联类似于宏函数,但是宏函数仅是文本的替换,而内联函数是在编译中完成的,不是在预处理完成的,优于宏函数.
注意事项:
- 内联增加程序的内存占用和缓存未命中的概率,进而影响程序性能。
- 频繁调用的简单函数适合内联,稀少调用的复杂函数不适合内联。
- 递归函数和虚函数不能内联,递归函数的跳出是由运行时上一层的结果决定的,编译时无法获知。
- inline关键字仅表示期望该函数进行内联优化,但是否适合内联则完全由编译器决定。有些函数没有加inline也会被编译器处理内联优化,而有些函数(如递归函数)即便加了inline也会被编译器忽略。
2. const
修饰基本类型: 当const修饰基本类型时,该基本类型的值在初始化之后就不能被改变。同时,在声明该基本类型时就必须设定初始值。
const int a = 10;
// a = 20; // 编译时报错,常量的值不可修改
// const int b; // 编译时报错,常量在声明时必须设定处置
修饰对象: 被const关键字修饰的对象、对象指针或对象引用,统称为常对象(不可变对象)。常对象只能调用常函数,不能调用非常函数;非常对象既可以调用常函数,也可以调用非常函数。
class MyClass {
public:
void modify() { /* 修改对象状态 */ }
void display() const { /* 不修改对象状态 */ }
};
const MyClass obj;
obj.display(); // 常对象 可以调用const成员函数
// obj.modify(); // 编译时报错,常对象不可以调用
修饰成员函数: 当const修饰成员函数时,这表示该成员函数不会修改它所属对象的任何成员变量(除非这些成员变量被声明为mutable)。这样的成员函数可以被const对象调用。
3. mutable
作用: 在被const修饰的成员函数中修改一个成员变量的值,需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制。
class MyClass {
public:
void func() const {
a = 4;
}
mutable int a;
};
4. volatile
作用: volatile关键字用于告知编译器某个变量的值可能会在程序的其他部分被改变,编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。
使用场景:
- 防止编译器优化:编译器通常会对代码进行优化,例如将变量的值缓存到寄存器中以提高执行效率。但某些变量(如硬件寄存器或共享变量)的值可能会被程序外部的事件或线程改变,此时不应当读取寄存器中的缓存数据。
- 硬件访问:当程序直接与硬件进行交互时(如操作寄存器),这些寄存器的值可能会被硬件在任何时候改变。为了保证从寄存器中读取到的值是最新的,应该将这些寄存器声明为volatile。
- 多线程编程:在多线程程序中,一个线程可能会修改另一个线程正在访问的变量。为了确保所有线程看到的变量值是最新的,应该将这些共享变量声明为volatile。注意:volatile并不保证原子性、不提供锁机制,在多线程环境中如需确保线程安全及操作的顺序,需要使用其他同步机制。
5. static
修饰成员变量: 使用static修饰一个类中的成员变量,可以使其成为被局限在类中使用的全局资源,可以把静态成员变量理解为被限制在类中使用的全局变量。
- 静态成员变量需要在类的外部单独定义和初始化。
class MyClass{
public:
MyClass(int data = 0):m_data(data){}
int m_data;
static int s_data; // 声明
};
int MyClass::s_data = 20; // 定义和初始化
- 对象中只包含普通成员变量,不包含静态成员变量。
MyClass a(10);
std::cout << "size= " << sizeof(a); // 结果为4。
- 静态成员变量也要受到类的访问控制限定符(public、private等)的约束。
- 在类的外部访问静态成员变量,可以使用 类名::静态成员变量 或使用 对象.静态成员变量 进行访问。
MyClass a(10);
std::cout << MyClass::s_data;
std::cout << a.s_data; // 对象实例中虽然不包含静态成员变量,但是可以进行使用。
修饰成员函数: 被 static 修饰的成员函数即为静态成员函数。
- 静态成员函数和普通的成员函数一样,既可以直接定义在类的内部,也可以定义在类的外部。
- 静态成员函数没有this指针,没有const属性,可以将其理解为被限制在类中使用的全局函数。
- 静态成员函数和静态成员变量一样,也要受到类的访问控制限定符的约束。
- 静态成员函数中只能访问静态成员;非静态成员函数中既可以访问静态成员,也可以访问非静态成员。
- 在类的外部访问静态成员函数,可以使用 类名::静态成员函数(实参表) 或 对象.静态成员函数(实参表)。
class MyClass{
public:
MyClass(int data = 0):m_data(data){}
int m_data;
static int s_data; // 声明
};
int MyClass::s_data = 20; // 定义和初始化
二、预处理指令
1. 预处理概述
预处理是C++编译过程的第一个阶段,由预处理器(Preprocessor)处理以#
开头的指令。这些指令在源代码编译前被替换或展开,不影响最终的语法结构,但会影响编译结果。
预处理指令的特点:
- 以
#
开头,独占一行(换行即结束) - 不加分号结尾
- 先于编译执行
2. 文件包含指令(#include)
(1) 语法与作用
#include <文件名> // 系统头文件
#include "文件名" // 用户自定义头文件
- 作用:将指定文件的内容插入到当前文件中,实现代码复用
(2) 两种包含方式的区别
方式 | 搜索路径 | 适用场景 |
---|---|---|
<文件名> | 系统头文件目录(如/usr/include ) | 包含标准库头文件 |
"文件名" | 当前目录→系统头文件目录 | 包含用户自定义头文件 |
(3) 示例
// 包含标准库头文件
#include <iostream> // 输入输出流
#include <vector> // 向量容器
#include <cmath> // 数学函数
// 包含自定义头文件
#include "myutils.h" // 假设存在myutils.h文件
#include "config/config.h" // 多级目录包含
3. 宏定义指令(#define)
(1) 简单宏(对象式宏)
- 语法
#define 宏名 替换内容
替换过程:预处理器遇到宏名时,直接替换为指定内容(文本替换)
示例
// 定义常量宏
#define PI 3.14159
#define MAX_SIZE 100
// 定义表达式宏(无参数)
#define SQUARE(x) (x * x) // 建议用括号避免优先级问题
int main() {
double radius = 5.0;
double area = PI * SQUARE(radius); // 替换为3.14159 * (5.0 * 5.0)
cout << "面积: " << area << endl;
// 宏定义的常量不能修改
// PI = 3.14; // 错误:宏不是变量
return 0;
}
(2) 带参数宏(函数式宏)
- 语法
#define 宏名(参数列表) 表达式
- 示例与注意事项
// 带参数宏(存在风险)
#define ADD(a, b) a + b // 错误写法:无括号
// 正确写法(添加括号)
#define ADD(a, b) ((a) + (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int result1 = ADD(5, 3) * 2; // 错误写法:5 + 3 * 2 = 11(预期16)
int result2 = ADD(5, 3) * 2; // 正确写法:(5 + 3) * 2 = 16
int x = 10, y = 5;
int maxVal = MAX(x, y); // (10 > 5 ? 10 : 5) = 10
// 宏与函数的区别:宏是文本替换,无类型检查
// 以下代码编译通过但可能出错
int* ptr1 = NULL, *ptr2 = &x;
int* maxPtr = MAX(ptr1, ptr2); // 指针比较,无实际意义
return 0;
}
(3) 特殊宏操作符
- #:将参数转换为字符串常量
- ##:连接两个标识符
#define STR(s) #s // 将参数转为字符串
#define CONCAT(a, b) a##b // 连接两个标识符
int main() {
cout << STR(Hello World) << endl; // 输出"Hello World"
int ab = CONCAT(a, b); // 相当于int ab;
return 0;
}
4. 条件编译指令(#if, #else, #elif, #endif)
(1) 基本语法
#if 常量表达式
// 代码块1(表达式为真时执行)
#elif 常量表达式
// 代码块2(表达式为真时执行)
#else
// 代码块3(以上都为假时执行)
#endif // 必须匹配
(2) 常用场景
- 根据编译器类型编译不同代码
#if defined(_MSC_VER) // Visual C++编译器
#include <windows.h>
#elif defined(__GNUC__) // GCC编译器
#include <unistd.h>
#endif
- 调试模式编译
#define DEBUG 1
int main() {
#if DEBUG
cout << "调试信息: 程序启动" << endl;
// 调试代码...
#endif
// 正式代码...
return 0;
}
- 避免头文件重复包含
// 方式1:宏守卫
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容...
#endif // MYHEADER_H
// 方式2:#pragma once(C++11,部分编译器支持)
#pragma once
// 头文件内容...
5. 其他预处理指令
(1) #undef:取消宏定义
#define VERSION 1.0
// 使用VERSION...
#undef VERSION // 取消VERSION宏定义
// 之后VERSION不再有效
(2) #line:设置行号和文件名
#line 100 "myfile.cpp" // 设置当前行号为100,文件名为myfile.cpp
(3) #error:生成编译错误
#if _WIN32 && __cplusplus < 201103L
#error "Windows平台需要C++11或更高版本"
#endif
(4) #pragma:编译器特定指令
- 常见用法
#pragma once // 防止头文件重复包含(部分编译器支持)
#pragma warning(disable: 4200) // 禁用特定警告
#pragma comment(lib, "user32.lib") // 链接指定库
- 编译器差异
- Visual C++:
#pragma
指令丰富,如#pragma managed
- GCC:使用
__attribute__
替代部分功能
- Visual C++:
6. 预处理与编译阶段的关系
(1) 编译流程
- 预处理:处理
#include
、#define
等指令,生成预处理后的文件 - 编译:将预处理文件转为汇编代码
- 汇编:汇编代码转为目标机器码
- 链接:目标码与库链接生成可执行文件
(2) 查看预处理结果
g++ -E source.cpp -o preprocessed.cpp # GCC预处理
cl /E source.cpp > preprocessed.cpp # Visual C++预处理
7. 预处理使用建议
- 头文件保护:使用#ifndef或#pragma once防止重复包含。
- 宏定义规范:
- 常量宏用大写字母(如MAX_SIZE)。
- 带参数宏添加括号避免优先级问题。
- 减少宏使用:优先使用const常量和内联函数替代宏。
- 条件编译谨慎使用:过多条件编译会降低代码可读性。
- 平台兼容性:用条件编译处理平台差异,而非直接修改代码。
三、类型转换
1. 类型转换概述
C++ 支持多种类型转换方式,用于在不同数据类型之间进行转换。根据转换的方式和安全性,可分为以下几类:
- 隐式转换(自动转换):由编译器自动完成,安全的小类型转大类型。
- 显式转换(强制转换):需要手动指定,可能存在数据丢失风险。
2. 隐式类型转换(自动转换)
- 基本数据类型转换规则
当操作数类型不同时,编译器会将小类型自动转换为大类型,遵循以下优先级顺序:bool → char → short → int → long → long long → float → double
- 示例:
#include <iostream>
using namespace std;
int main() {
// 布尔值转换为整数
bool flag = true; // true转换为1,false转换为0
int num = flag;
cout << "bool→int: " << num << endl; // 输出1
// 字符转换为整数
char ch = 'A'; // 'A'的ASCII码为65
int ascii = ch;
cout << "char→int: " << ascii << endl; // 输出65
// 整数转换为浮点数
int intVal = 10;
double doubleVal = intVal; // 10 → 10.0
cout << "int→double: " << doubleVal << endl; // 输出10.0
// 混合类型运算中的转换
int a = 5;
double b = 2.5;
double result = a + b; // a先转换为double(5.0),再运算
cout << "混合运算: " << result << endl; // 输出7.5
return 0;
}
- 注意事项:
- 隐式转换不会丢失数据精度(如int→double)
- 布尔值true转换为1,false转换为0
- 字符转换为整数时使用 ASCII 码值
3. C 风格强制转换(C-Style Cast)
- 语法
(目标类型) 表达式; // 或 目标类型(表达式);
功能与风险
可用于以下场景,但缺乏类型安全检查:- 基本类型转换:如int→double、double→int(可能丢失精度)
- 指针类型转换:如void*→int*
- 类指针转换:如基类指针→子类指针(不安全,需确保类型兼容)
示例
#include <iostream>
using namespace std;
int main() {
// 基本类型转换(可能丢失精度)
double d = 3.14;
int i = (int)d; // 3.14→3,小数部分直接截断
cout << "double→int: " << i << endl; // 输出3
// 无符号数与有符号数转换
int signedNum = -5;
unsigned int unsignedNum = (unsigned int)signedNum;
cout << "有符号→无符号: " << unsignedNum << endl; // 输出4294967291(补码表示)
// 指针转换
int* intPtr = new int(100);
void* voidPtr = (void*)intPtr; // int*→void*
int* restoredPtr = (int*)voidPtr; // void*→int*
cout << "*restoredPtr: " << *restoredPtr << endl; // 输出100
// 危险转换(基类→子类,无安全检查)
class Base {};
class Derived : public Base {};
Base* basePtr = new Derived();
Derived* derivedPtr = (Derived*)basePtr; // 合法但无安全检查
// 若basePtr实际指向Base对象,会导致未定义行为
return 0;
}
4. C++ 风格类型转换(C++-Style Cast)
C++ 提供了 4 种类型转换操作符,增强了类型转换的安全性和可读性。
- static_cast:用于有意义的类型转换(如基本类型、继承体系等)。
- dynamic_cast:用于继承体系中的安全向下转型。
- const_cast:用于移除const/volatile限定符。
- reinterpret_cast:用于底层位模式重新解释。
(1) static_cast
- 语法
static_cast<目标类型>(表达式);
适用场景
基本类型转换:如int↔double、char↔int
继承体系中的向上转型(基类→子类,安全)
类中的非虚函数转换(需确保类型兼容)示例
#include <iostream>
using namespace std;
class Animal {};
class Dog : public Animal {};
int main() {
// 基本类型转换
int i = 10;
double d = static_cast<double>(i); // 10→10.0
cout << "int→double: " << d << endl;
double pi = 3.14159;
int piInt = static_cast<int>(pi); // 截断为3
cout << "double→int: " << piInt << endl;
// 向上转型(安全)
Dog dog;
Animal* animalPtr = static_cast<Animal*>(&dog); // Dog*→Animal*
// 向上转型始终安全,因为Dog是Animal的子类
// 类中的类型转换(需确保安全)
class A { public: virtual void func() {} };
class B : public A { public: void func() override {} };
A* aPtr = new B();
B* bPtr1 = static_cast<B*>(aPtr); // 合法,aPtr实际指向B对象
// B* bPtr2 = static_cast<B*>(new A()); // 危险,A不是B的子类(编译通过但运行时可能出错)
return 0;
}
(2) dynamic_cast
- 语法
dynamic_cast<目标类型>(表达式);
用于指针时,转换失败返回nullptr
用于引用时,转换失败抛出bad_cast异常(需#include <typeinfo>
)
适用场景
继承体系中的向下转型(安全检查)
检查对象的实际类型示例
#include <iostream>
#include <typeinfo> // 用于typeid
using namespace std;
class Animal {
public:
virtual void speak() {} // 虚函数,必须存在才能使用dynamic_cast
};
class Dog : public Animal {
public:
void speak() override { cout << "汪汪" << endl; }
void bark() { cout << "狗叫" << endl; }
};
class Cat : public Animal {
public:
void speak() override { cout << "喵喵" << endl; }
};
int main() {
// 指针版本
Animal* animalPtr1 = new Dog();
Dog* dogPtr = dynamic_cast<Dog*>(animalPtr1); // 成功,返回Dog*
if (dogPtr) {
dogPtr->bark(); // 调用Dog特有的方法
}
Animal* animalPtr2 = new Cat();
Dog* invalidPtr = dynamic_cast<Dog*>(animalPtr2); // 失败,返回nullptr
if (!invalidPtr) {
cout << "转换失败:不是Dog类型" << endl;
}
// 引用版本
try {
Animal& animalRef = *new Cat();
Dog& dogRef = dynamic_cast<Dog&>(animalRef); // 抛出异常
} catch (const bad_cast& e) {
cout << "异常:" << e.what() << endl; // 输出"bad_cast"
}
// 类型检查
if (typeid(*animalPtr1) == typeid(Dog)) {
cout << "对象是Dog类型" << endl;
}
return 0;
}
(3) const_cast
- 语法
仅用于添加或移除const/volatile限定符,目标类型必须与原类型相同,仅限定符不同。
const_cast<目标类型>(表达式);
- 适用场景
修改const变量(需确保变量实际非const);函数参数类型匹配(如移除const以调用非const函数)。
- 示例
#include <iostream>
using namespace std;
void nonConstFunc(int* ptr) {
*ptr = 200;
}
int main() {
// 移除const限定符(危险操作)
const int num = 100;
int* nonConstPtr = const_cast<int*>(&num);
*nonConstPtr = 200; // 未定义行为,num实际仍是const
cout << "num: " << num << endl; // 输出可能仍为100(取决于编译器优化)
// 函数参数类型匹配
const int val = 50;
// nonConstFunc(&val); // 错误:参数类型不匹配
nonConstFunc(const_cast<int*>(&val)); // 强制转换,危险操作
// 添加const限定符
int x = 10;
const int& constRef = const_cast<const int&>(x); // 添加const
return 0;
}
(4) reinterpret_cast
- 语法
重新解释二进制位模式,不改变内存数据;用于底层指针转换(如函数指针、不同类型指针互转)。
reinterpret_cast<目标类型>(表达式);
适用场景
不同类型指针转换(如int*↔char*)
函数指针类型转换
基本类型与指针互转(如int↔void*)
示例
#include <iostream>
using namespace std;
// 函数指针类型
typedef void (*FuncPtr)();
void func() { cout << "函数被调用" << endl; }
int main() {
// 指针类型转换(无实际意义,仅演示)
int num = 12345;
char* charPtr = reinterpret_cast<char*>(&num);
cout << "int的字节表示: ";
for (int i = 0; i < sizeof(int); i++) {
cout << (unsigned int)(*(charPtr + i)) << " ";
}
cout << endl;
// 函数指针转换
int* intPtr = new int(100);
FuncPtr funcPtr = reinterpret_cast<FuncPtr>(intPtr);
// funcPtr(); // 调用未定义地址,导致程序崩溃
// 指针与整数互转
void* voidPtr = #
int ptrValue = reinterpret_cast<int>(voidPtr);
cout << "指针地址转为整数: " << ptrValue << endl;
return 0;
}
5. 类型转换总结对比
转换方式 | 语法 | 安全性 | 主要用途 |
---|---|---|---|
隐式转换 | 自动完成 | 高(小→大) | 基本类型自动提升 |
C 风格转换 | (T)expr 或 T(expr) | 低 | 通用强制转换(缺乏类型检查 |
static_cast | static_cast<T>(expr) | 中 | 有意义的类型转换(基本类型、继承) |
dynamic_cast | dynamic_cast<T>(expr) | 高 | 继承体系中的安全转型 |
const_cast | const_cast<T>(expr) | 中(危险) | 移除 / 添加const限定符 |
reinterpret_cast | reinterpret_cast<T>(expr) | 低(危险) | 底层位模式重新解释 |
6. 类型转换建议
- 优先使用隐式转换:避免不必要的强制转换,减少错误风险。
- 明确使用 C++ 风格转换:根据场景选择合适的转换操作符,提高代码可读性。
- 避免滥用 const_cast:仅在确有必要时使用,且需确保操作安全。
- dynamic_cast 用于继承体系:向下转型时必须使用,防止类型不匹配。
- reinterpret_cast 谨慎使用:仅用于底层编程,避免在应用层使用。
四、内存管理
1. 内存区域划分
C++程序运行时内存分为四个主要区域:
栈(Stack)
- 特点:自动分配释放,存储局部变量、函数参数
- 示例:
int a = 10;
变量a存储在栈区
堆(Heap)
- 特点:手动分配释放(
new
/delete
),存储动态对象 - 示例:
int* ptr = new int(10);
指针ptr指向堆区数据
- 特点:手动分配释放(
全局/静态区
- 特点:存储全局变量和静态变量,程序结束后释放
常量区
- 特点:存储常量字符串,程序结束后释放
2. 动态内存分配(new/delete)
(1)基本用法
// 分配单个对象
int* num = new int; // 分配int空间,值未初始化
int* numInit = new int(10); // 分配并初始化为10
// 分配数组
double* arr = new double[5]; // 分配5个double的数组
// 释放内存
delete num; // 释放单个对象
delete[] arr; // 释放数组(必须用[])
// 分配对象
class Person { /*...*/ };
Person* p = new Person(); // 调用构造函数
delete p; // 调用析构函数
(2)内存泄漏风险
void memoryLeakDemo() {
int* ptr = new int(10);
// 忘记调用delete ptr;
// 导致内存泄漏
}
3. 智能指针(C++11)
见C++新特性章节。
4. 内存管理建议
- 优先使用智能指针:避免手动管理内存,防止泄漏
- 遵循RAII原则:资源获取即初始化,通过析构函数释放
- 避免野指针:删除指针后设为
nullptr
- new/delete匹配:
new
与delete
、new[]
与delete[]
对应
五、异常处理
1. 异常处理语法
(1)try/catch/throw基础
#include <iostream>
#include <string>
double divide(double a, double b) {
if (b == 0) {
throw std::string("除数不能为零"); // 抛出异常
}
return a / b;
}
int main() {
double x, y;
std::cin >> x >> y;
try {
double result = divide(x, y);
std::cout << "结果: " << result << std::endl;
} catch (const std::string& e) { // 捕获string类型异常
std::cout << "错误: " << e << std::endl;
} catch (const std::exception& e) { // 捕获标准异常
std::cout << "标准异常: " << e.what() << std::endl;
} catch (...) { // 捕获所有其他异常
std::cout << "未知异常" << std::endl;
}
return 0;
}
(2)异常传播
void func3() {
throw std::runtime_error("func3错误");
}
void func2() {
func3(); // 异常传播到func2
}
void func1() {
func2(); // 异常传播到func1
}
int main() {
try {
func1(); // 异常传播到main
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
return 0;
}
2. 标准异常类
C++标准异常继承体系:
exception
├─ runtime_error
│ ├─ range_error
│ ├─ overflow_error
│ └─ underflow_error
├─ logic_error
│ ├─ invalid_argument
│ ├─ out_of_range
│ └─ bad_cast
└─ bad_alloc //new 失败时抛出
3. 自定义异常
#include <stdexcept>
#include <string>
class MyException : public std::exception {
private:
std::string message;
public:
MyException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
};
void processData(int data) {
if (data < 0) {
throw MyException("数据不能为负数");
}
// 处理数据...
}
4. 异常处理建议
- 异常用于错误处理:不用于正常流程控制
- 异常安全:确保异常抛出后资源正确释放(RAII)
- 异常规格:使用
noexcept
声明无异常函数 - 优先捕获具体异常:先捕获派生类异常,再捕获基类
六、线程管理
七、线程同步
1. 互斥量(mutex)
- 基础使用方法
std::mutex mtx;
mtx.lock(); // 加锁
/* 临界区 */
mtx.unlock(); // 解锁
- RAII形式自动加解锁(C++11)
std::lock_guard<std::mutex> lock(mtx); // 自动加锁解锁
- 死锁避免
- 互斥锁上锁后不得再次进行加锁,否则会导致死锁。
- 避免业务逻辑中循环等待情况的出现。
- 对两个互斥锁同时加锁时,建议使用
std::lock(mtx1,mtx2);
。
2. 条件变量
3. 原子类型(atomic)
见C++11新特性。
4. 同步建议
- 最小化临界区:仅在必要时加锁,减少锁竞争。
- 避免嵌套锁:必须时按固定顺序加锁,防止死锁。
- 优先使用原子操作:轻量级同步,无锁开销。
- 线程安全设计:数据封装,避免共享可变状态。