02-CPP基础语法
数据类型
C++ 是强类型语言,每个变量必须声明类型。类型决定了内存占用、取值范围和支持的操作。
整型
整型用于存储整数。根据内存大小和是否支持负数选择合适的类型。
char // 1字节, -128 ~ 127
short // 2字节, -32768 ~ 32767
int // 4字节, -2147483648 ~ 2147483647(常用)
long // 4/8字节(平台相关)
long long // 8字节(大整数)
unsigned // 无符号修饰符,范围翻倍但只能表示非负数
类型选择建议:优先使用 int,需要大范围时用 long long,明确非负时用 unsigned。
浮点型
浮点型用于存储小数。精度和内存成正比。
float // 4字节, 7位精度(节省内存)
double // 8字节, 15位精度(默认选择)
long double // 更高精度(科学计算)
注意:浮点数存在精度误差,避免用 == 比较,改用误差范围判断。
其他类型
bool // true/false,逻辑值
char // 单字符,ASCII 编码
void // 无类型,用于函数返回值
类型大小查询
sizeof 运算符返回类型或变量占用的字节数,编译期计算。
sizeof(int) // 通常为 4
sizeof(double) // 通常为 8
sizeof(variable) // 变量占用的字节数
变量和常量
变量声明
变量是存储数据的命名内存空间。声明变量时应初始化,避免未定义行为。
int age = 25; // 声明并初始化
int height; // 仅声明(值未定义,危险)
auto x = 10; // 自动类型推导(C++11)
int a{5}; // 列表初始化(推荐,防止窄化转换)
命名规范:使用有意义的名称(studentAge 而非 a),驼峰命名法,避免关键字。
常量
常量的值不可修改,编译器会强制检查。使用常量可提高代码可读性和安全性。
const int MAX = 100; // 运行时常量,可用变量初始化
constexpr int SIZE = 10; // 编译时常量,必须编译期可计算
#define PI 3.14159 // 宏常量(不推荐,无类型检查)
最佳实践:优先使用 constexpr,其次 const,避免 #define。
作用域
作用域决定变量的可见范围和生命周期。内层作用域可访问外层变量,但反之不行。
int global = 1; // 全局作用域,程序结束时销毁
void func() {
int local = 2; // 局部作用域,函数结束时销毁
{
int block = 3; // 块作用域,出块时销毁
// 可访问 global, local, block
}
// 可访问 global, local,但不可访问 block
}
原则:尽量缩小变量作用域,减少命名冲突和意外修改。
运算符
运算符是执行特定操作的符号。掌握运算符优先级和结合性是编写正确表达式的基础。
算术运算符
基本数学运算。注意整数除法会截断小数部分。
+ - * / % // 加减乘除取模(%仅用于整数)
++ -- // 自增自减(前置:先改后用,后置:先用后改)
+= -= *= /= %= // 复合赋值(x += 5 等价于 x = x + 5)
示例:5 / 2 结果为 2(整数除法),5.0 / 2 结果为 2.5(浮点除法)。
关系运算符
比较运算,返回布尔值。
== != > < >= <= // 相等、不等、大于、小于、大于等于、小于等于
注意:赋值是 =,比较是 ==,初学者易混淆。
逻辑运算符
组合条件判断,支持短路求值。
&& || ! // 与(都为真)、或(至少一个为真)、非(取反)
短路求值:a && b 中若 a 为假则不计算 b;a || b 中若 a 为真则不计算 b。
位运算符
直接操作二进制位,常用于底层编程、标志位管理和性能优化。
& | ^ ~ // 按位与、或、异或、取反
<< >> // 左移(乘2^n)、右移(除2^n)
应用:x & 1 判断奇偶,x << 1 快速乘2,x | (1 << n) 设置第n位。
运算符优先级(高到低)
优先级决定运算顺序,不确定时用括号明确。
()- 括号! ~ ++ --- 一元运算符* / %- 乘除取模+ -- 加减<< >>- 移位< <= > >=- 关系== !=- 相等&- 按位与^- 按位异或|- 按位或&&- 逻辑与||- 逻辑或= += -=- 赋值
建议:复杂表达式加括号,提高可读性。
控制结构
控制结构决定程序执行流程。合理使用可提高代码可读性和效率。
条件语句
根据条件执行不同代码分支。
// if-else:基本条件判断
if (condition) {
// condition为真时执行
} else if (condition2) {
// condition为假且condition2为真时执行
} else {
// 以上都为假时执行
}
// switch:多分支选择(值必须是整型或枚举)
switch (value) {
case 1:
// value == 1
break; // 必须break,否则继续执行下一case
case 2:
// value == 2
break;
default:
// 其他情况
}
// 三元运算符:简洁的条件表达式
result = (condition) ? value1 : value2; // condition真取value1,假取value2
技巧:switch 比多个 if-else 更高效;三元运算符适合简单赋值。
循环语句
重复执行代码块,直到条件不满足。
// for循环:次数确定时使用
for (int i = 0; i < n; ++i) { // 初始化; 条件; 更新
// 循环体
}
// while循环:次数不确定时使用
while (condition) {
// 先判断条件,再执行
}
// do-while循环:至少执行一次
do {
// 先执行,再判断条件
} while (condition);
// 范围for(C++11):遍历容器
for (auto& item : container) { // auto自动推导类型,&避免拷贝
// 处理item
}
选择:已知次数用 for,未知次数用 while,需要先执行用 do-while。
跳转语句
改变正常的顺序执行流程。
break; // 跳出当前循环或switch
continue; // 跳过本次循环剩余部分,进入下次迭代
return; // 结束函数并返回值
goto label; // 跳转到标签(破坏结构化,强烈不推荐)
最佳实践:能用 break/continue 就不用 goto;多层循环跳出可用标志变量。
函数
函数是可重用的代码块,封装特定功能。良好的函数设计可提高代码复用性和可维护性。
函数定义
函数由返回类型、名称、参数列表和函数体组成。
// 声明(告诉编译器函数存在)
int add(int a, int b);
// 定义(实现函数功能)
int add(int a, int b) {
return a + b;
}
// 内联函数:建议编译器在调用处展开,减少函数调用开销
inline int square(int x) {
return x * x; // 适合短小、频繁调用的函数
}
// 默认参数:调用时可省略
void print(int value, int base = 10); // 默认十进制
print(100); // 使用默认参数
print(100, 16); // 使用十六进制
// 函数重载:同名函数,不同参数
int max(int a, int b);
double max(double a, double b); // 根据参数类型选择
注意:默认参数只能在声明中指定,且从右往左连续。
参数传递
不同传递方式影响性能和语义。
// 值传递:复制参数,函数内修改不影响原变量
void func1(int x); // 小对象适用
// 引用传递:直接操作原变量,无拷贝开销
void func2(int& x); // x的修改会影响实参
// 指针传递:传递地址,可传NULL表示可选
void func3(int* x); // 需判空:if (x) {...}
// const引用:只读访问,避免拷贝(推荐)
void func4(const int& x); // 大对象传参的最佳选择
选择建议:小对象(如int)值传递,大对象(如string)const引用,需修改用非const引用。
Lambda表达式(C++11)
匿名函数,常用于算法和回调。语法:[捕获] (参数) { 函数体 }
// 基本Lambda
auto add = [](int a, int b) { return a + b; };
int result = add(3, 4); // 7
// 捕获外部变量
int x = 10;
auto lambda1 = [x](int y) { return x + y; }; // 值捕获(只读)
auto lambda2 = [&x](int y) { x += y; }; // 引用捕获(可修改)
auto lambda3 = [=](int y) { return x + y; }; // 捕获所有(值)
auto lambda4 = [&](int y) { x += y; }; // 捕获所有(引用)
应用:std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
数组
数组是固定大小的连续内存块,存储相同类型的元素。索引从0开始。
静态数组
编译期确定大小,分配在栈上,访问快但大小固定。
int arr[5] = {1, 2, 3, 4, 5}; // 完整初始化
int arr2[] = {1, 2, 3}; // 自动推导大小为3
int arr3[5] = {}; // 全部初始化为0
int arr4[5] = {1, 2}; // {1, 2, 0, 0, 0},未指定的元素为0
// 多维数组:本质是数组的数组
int matrix[3][4]; // 3行4列,matrix[i][j]访问
int cube[2][3][4]; // 三维数组
注意:C++不检查数组越界,arr[100] 可能导致段错误或未定义行为。
数组操作
arr[0] = 10; // 访问/修改第0个元素
int size = sizeof(arr) / sizeof(arr[0]); // 计算元素个数
// 遍历数组
for (int i = 0; i < size; ++i) {
cout << arr[i] << " ";
}
// C++11范围for(更简洁)
for (int val : arr) {
cout << val << " ";
}
限制:数组传参会退化为指针,丢失大小信息。推荐使用 std::array(C++11)或 std::vector。
字符串
C++提供两种字符串:C风格(字符数组)和C++风格(string类),推荐使用后者。
C风格字符串
以空字符 '\0' 结尾的字符数组,操作需小心缓冲区溢出。
char str[] = "Hello"; // 自动添加'\0',实际占6字节
char* ptr = "World"; // 字符串字面量,不可修改
// 常用函数(#include <cstring>)
strlen(str); // 长度(不含'\0')
strcpy(dest, src); // 复制(需确保dest足够大)
strcat(dest, src); // 连接(需确保dest足够大)
strcmp(str1, str2); // 比较(0相等,<0小于,>0大于)
风险:不检查长度,易溢出。现代C++避免使用。
C++字符串(推荐)
std::string 类自动管理内存,提供丰富操作,安全便捷。
#include <string>
string s1 = "Hello";
string s2 = s1 + " World"; // 连接(运算符重载)
s1.length(); // 长度(size()等价)
s1.empty(); // 是否为空
s1.substr(0, 5); // 提取子串[0,5)
s1.find("llo"); // 查找位置(未找到返回string::npos)
s1.replace(0, 2, "He"); // 替换[0,2)为"He"
s1[0] = 'h'; // 索引访问
s1.c_str(); // 转C风格字符串
优势:自动扩容、安全、支持运算符、STL兼容。
指针和引用
指针和引用是C++中操作内存地址的机制,是C++强大但也易出错的特性。
指针
指针存储变量的内存地址,通过地址间接访问变量。
int value = 42;
int* ptr = &value; // &取地址运算符
int deref = *ptr; // *解引用运算符,获取指针指向的值
// 空指针:不指向任何对象
int* p = nullptr; // C++11推荐,类型安全
int* p2 = NULL; // 传统方式,实际是0
int* p3 = 0; // 更古老的方式
// 指针运算:适用于数组
int arr[5];
int* p = arr; // 数组名自动转换为指向首元素的指针
p++; // 移动到下一个元素(+sizeof(int)字节)
p += 2; // 跳过2个元素
int diff = p - arr; // 指针差=元素个数
危险:野指针(未初始化)、悬空指针(对象已销毁)、内存泄漏(忘记释放)。
引用
引用是变量的别名,必须初始化且不可重新绑定,比指针安全。
int value = 42;
int& ref = value; // 引用必须初始化,之后ref就是value的别名
ref = 100; // 修改ref等同于修改value
// const引用:只读引用,常用于函数参数
const int& cref = value; // 不能通过cref修改value
cref = 200; // 错误!const引用不可修改
区别:指针可空可重新赋值,引用非空不可重新绑定;引用更安全,指针更灵活。
动态内存
运行时分配内存,用于不确定大小或需要长生命周期的数据。
// 分配
int* p = new int(42); // 分配单个int并初始化为42
int* arr = new int[10]; // 分配10个int的数组
// 释放(必须与分配配对)
delete p; // 释放单个对象
delete[] arr; // 释放数组(必须用delete[])
// 智能指针(C++11,强烈推荐):自动管理内存,避免泄漏
#include <memory>
unique_ptr<int> up(new int(42)); // 独占所有权
shared_ptr<int> sp = make_shared<int>(42); // 共享所有权(引用计数)
weak_ptr<int> wp = sp; // 弱引用,不增加引用计数
原则:优先使用智能指针,避免手动 new/delete;RAII(资源获取即初始化)模式。
结构体和联合体
结构体
结构体将不同类型的数据组合成一个自定义类型,是面向对象的基础。
struct Point {
int x; // 成员变量
int y;
};
Point p = {10, 20}; // 初始化
p.x = 30; // 访问成员
Point p2 = p; // 结构体可直接赋值(浅拷贝)
用途:表示复合数据,如坐标、日期、学生信息等。C++中 struct 和 class 几乎相同,仅默认访问权限不同。
联合体
联合体的所有成员共享同一块内存,同一时刻只能使用一个成员,节省空间。
union Data {
int i; // 所有成员共享内存
float f; // 修改一个会影响其他
char c;
};
Data d;
d.i = 10; // 使用int成员
d.f = 3.14; // 现在int成员的值无效
注意:联合体大小等于最大成员的大小。常用于类型转换或节省内存。
枚举
枚举定义一组命名的整数常量,提高代码可读性。
enum Color { RED, GREEN, BLUE }; // 默认值:0, 1, 2
Color c = RED;
int val = c; // 可隐式转换为int
// 强类型枚举(C++11,推荐):类型安全,不可隐式转换
enum class Status { OK, ERROR, PENDING };
Status s = Status::OK; // 必须加作用域
// int x = s; // 错误!不可隐式转换
int x = static_cast<int>(s); // 必须显式转换
优势:增强可读性,避免魔法数字;强类型枚举防止命名冲突和隐式转换。
类型转换
类型转换将一种类型的值转换为另一种类型。C++提供隐式和显式两种方式。
隐式转换
编译器自动执行的转换,通常是安全的(小转大、整数转浮点)。
int i = 10;
double d = i; // int自动转double,无精度损失
char c = 'A';
int x = c; // char转int,值为ASCII码
风险:大转小(如 int 转 char)可能丢失数据,编译器可能警告。
显式转换
程序员明确指定的转换,用于不安全或非标准的转换。
// C风格(不推荐,无类型检查)
int i = (int)3.14; // 截断为3
char* p = (char*)&i; // 危险的类型双关
// C++风格(推荐,明确意图,编译器检查)
static_cast<int>(3.14); // 标准类型转换,编译期检查
const_cast<int&>(constVar); // 移除const属性(谨慎使用)
dynamic_cast<Derived*>(basePtr); // 运行时多态类型转换(需RTTI)
reinterpret_cast<int*>(&f); // 重新解释位模式(底层操作)
选择:优先 static_cast,需要运行时检查用 dynamic_cast,避免 reinterpret_cast。
预处理器
预处理器在编译前处理源代码,进行文本替换和条件编译。
宏定义
宏是文本替换,不进行类型检查,现代C++推荐用 const 和 inline 函数代替。
#define PI 3.14159 // 常量宏(推荐用const)
#define MAX(a, b) ((a) > (b) ? (a) : (b)) // 函数宏(推荐用inline或模板)
#define SQUARE(x) ((x) * (x)) // 注意加括号,防止优先级问题
陷阱:SQUARE(x+1) 展开为 ((x+1) * (x+1)),可能导致副作用重复计算。
条件编译
根据条件选择性地编译代码,常用于平台差异、调试代码、头文件保护。
// 调试代码
#ifdef DEBUG
cout << "Debug info" << endl;
#endif
// 头文件保护(防止重复包含)
#ifndef HEADER_H
#define HEADER_H
// 头文件内容
#endif
// 平台相关代码
#if defined(WIN32)
// Windows特定代码
#elif defined(__linux__)
// Linux特定代码
#else
// 其他平台
#endif
预定义宏
编译器提供的内置宏,用于调试和诊断。
__FILE__ // 当前文件名(字符串)
__LINE__ // 当前行号(整数)
__DATE__ // 编译日期(字符串:"Mmm dd yyyy")
__TIME__ // 编译时间(字符串:"hh:mm:ss")
__func__ // 当前函数名(C++11,字符串)
示例:cerr << "Error at " << __FILE__ << ":" << __LINE__ << endl;
命名空间
命名空间解决命名冲突问题,将代码逻辑分组。标准库在 std 命名空间中。
// 定义命名空间
namespace MySpace {
int value = 10;
void func() { /* ... */ }
}
// 使用(需要完全限定名)
MySpace::value;
MySpace::func();
// using声明:引入特定名称
using MySpace::value;
value = 20; // 现在可直接使用
// using指令:引入整个命名空间(不推荐,污染全局)
using namespace MySpace;
value = 30;
func();
// 嵌套命名空间(C++17简化语法)
namespace A::B::C { // 等价于 namespace A { namespace B { namespace C { }}}
int x;
}
建议:头文件避免 using namespace;实现文件可局部使用;明确指定 std:: 提高可读性。
输入输出
C++提供流式输入输出,类型安全且可扩展。
标准输入输出
cin/cout 分别用于输入输出,位于 <iostream> 头文件。
#include <iostream>
using namespace std;
// 输出
cout << "Hello" << endl; // endl刷新缓冲区并换行
cout << "Value: " << x << '\n'; // '\n'仅换行,更高效
cout << x << " " << y << '\n'; // 链式输出
// 输入
int n;
cin >> n; // 跳过空白字符读取
cin >> x >> y >> z; // 链式输入
// 格式化输出
#include <iomanip>
cout << fixed << setprecision(2) << 3.14159; // 3.14(保留两位小数)
cout << setw(10) << x; // 设置宽度为10
cout << hex << 255; // 十六进制:ff
技巧:输入失败时 cin 进入错误状态,用 cin.clear() 恢复。
文件输入输出
文件流继承标准流,用法类似。位于 <fstream> 头文件。
#include <fstream>
// 写文件
ofstream out("file.txt"); // 打开文件写入
if (out.is_open()) { // 检查是否成功
out << "Hello\n";
out.close(); // 关闭文件(可省略,析构自动关闭)
}
// 读文件
ifstream in("file.txt");
if (in.is_open()) {
string line;
while (getline(in, line)) { // 按行读取
cout << line << '\n';
}
in.close();
}
// 追加写入
ofstream out("file.txt", ios::app);
// 二进制读写
ofstream binOut("data.bin", ios::binary);
注意:文件流对象作用域结束时自动关闭;检查 is_open() 确保操作成功。
常见陷阱
初学者和经验丰富的程序员都可能遇到的典型错误。
// 1. 整数除法截断
int a = 5 / 2; // 结果是2(不是2.5),小数部分被截断
double b = 5 / 2; // 仍是2.0(先算5/2=2,再转double)
double c = 5.0 / 2; // 正确:2.5(至少一个操作数是浮点)
// 2. 数组越界
int arr[5];
arr[5] = 10; // 未定义行为!合法索引是0-4
arr[-1] = 10; // 未定义行为!负索引
// 3. 悬空指针(野指针)
int* p = new int(10);
delete p; // 释放内存
*p = 20; // 未定义行为!访问已释放内存
p = nullptr; // 良好习惯:释放后置空
// 4. 内存泄漏
void leak() {
int* p = new int(10);
return; // 忘记delete,内存泄漏!
}
// 5. 未初始化变量
int x; // 包含垃圾值(未定义)
cout << x; // 输出不可预测
int y = 0; // 正确:初始化为0
// 6. switch缺少break
switch (x) {
case 1:
cout << "One";
// 没有break,会"穿透"到case 2!
case 2:
cout << "Two";
break;
}
// 7. 赋值与比较混淆
if (x = 5) { // 赋值!x变为5,条件永远为真
// ...
}
if (x == 5) { // 正确:比较
防范:开启编译器警告(-Wall -Wextra),使用静态分析工具,养成良好习惯。
编码规范
一致的编码风格提高代码可读性和可维护性。
命名规范
清晰的命名是自文档化的第一步。
// 变量/函数:小驼峰(camelCase)
int studentAge;
string firstName;
void calculateSum();
// 常量:全大写+下划线
const int MAX_SIZE = 100;
const double PI = 3.14159;
// 类/结构体:大驼峰(PascalCase)
class StudentInfo;
struct Point2D;
// 宏:全大写+下划线
#define MAX_BUFFER_SIZE 1024
// 私有成员:前缀下划线(可选)
class MyClass {
int _privateVar;
};
代码风格
// 缩进:4空格(或2空格/Tab,团队统一即可)
if (condition) {
statement;
}
// 括号风格:K&R(开括号同行)或Allman(开括号另起一行)
// K&R(推荐,节省行数)
if (condition) {
// ...
}
// Allman
if (condition)
{
// ...
}
// 一行一条语句
int a = 1;
int b = 2;
// 避免魔法数字(用命名常量)
const int MAX_STUDENTS = 100;
if (count > MAX_STUDENTS) { // 清晰明了
// ...
}
// 适当的空行分隔逻辑块
void func() {
// 初始化
int x = 0;
// 处理逻辑
for (...) {
// ...
}
// 清理
cleanup();
}
最佳实践
编写高质量C++代码的核心原则。
优先使用 const:不变的值用
const或constexpr,防止意外修改优先使用引用传参:大对象用
const&,避免拷贝;小对象可值传递避免全局变量:减少耦合和副作用,改用参数传递或单例模式
RAII原则:资源获取即初始化,利用析构函数自动释放资源
避免裸指针:用智能指针(
unique_ptr/shared_ptr)管理动态内存初始化所有变量:声明时立即初始化,优先用列表初始化
{}使用范围for:遍历容器用
for (auto& item : container),简洁安全善用auto:复杂类型用
auto,但不滥用(简单类型明确写出)异常安全:使用RAII和智能指针,确保异常时资源不泄漏
代码可读性优先:清晰的代码胜过聪明的技巧,他人能快速理解最重要
核心思想:写安全、清晰、高效的代码,按这个优先级排序。
性能优化技巧
避免不必要的拷贝
C++对象默认值传递会触发拷贝,大对象拷贝成本高。使用引用和移动语义可显著提升性能。
// ❌ 低效:拷贝大对象
void process(std::string str) { // 拷贝整个字符串
// ...
}
// ✅ 高效:const引用(只读)
void process(const std::string& str) { // 无拷贝
// ...
}
// ✅ 高效:移动语义(转移所有权)
void process(std::string&& str) { // 移动而非拷贝
// ...
}
// ✅ 返回值优化(RVO)
std::string createString() {
std::string result = "Hello";
return result; // 编译器会优化掉拷贝
}
// ❌ 低效:范围for的拷贝
for (std::string item : vec) { // 每次拷贝string
// ...
}
// ✅ 高效:引用
for (const auto& item : vec) { // 无拷贝
// ...
}
关键点:
大对象传参用
const&修改参数用非const引用
&转移所有权用右值引用
&&范围for必须加
&或const&信任编译器的返回值优化(RVO/NRVO)