C++ 基础入门
本人写了很多c++的服务器和客户端代码,这篇文章主要是想帮助初学者快速入门c++.这样就能快速阅读我的源码,其实不难c++只是比c多了些特性,其实不难,你们就理解为有更多的方式修改函数和调用函数的方式和重写函数
C++ 基础入门
目录
- 教程简介
- C++ 初步了解
- 2.1 C++ 简介
- 2.2 我的第一个程序 (“Hello, World!”)
- 2.3 C++ 标准库
- 2.4 编译器及编译环境
- 2.5 注释:给代码添加说明
- C++ 数据类型
- 3.1 基本数据类型
- 3.2 数据类型占用空间大小
- 3.3 变量:存储数据的容器
- 3.4 常量:不可改变的值
- 运算符:执行操作的符号
- 4.1 算术运算符
- 4.2 关系运算符
- 4.3 逻辑运算符
- 4.4 位运算符 (进阶)
- 头文件:代码的“说明书”
- 输入输出流:与程序交互
- 分支语句:让程序做出选择
- 7.1 if-else 语句
- 7.2 switch 语句
- 循环:重复执行代码块
- 8.1 for 循环
- 8.2 while 循环
- 8.3 do-while 循环
- 8.4 嵌套循环
- 8.5 循环控制语句 (break, continue)
- 数组:存储同类型数据的集合
- 字符串:处理文本数据
- 10.1 C 风格字符串 (cstring)
- 10.2 C++ string 类
- 函数:代码的模块化
- 总结与展望
1. 教程简介
欢迎来到 C++ 的世界!本文旨在为编程新手,特别是对 C++ 感兴趣的小白,提供一个快速入门的基础教程。本教程由 “那些年丶我们逃过的课” 在参考各类资料的基础上精心总结而成,力求通俗易懂,助你迈出 C++ 学习的第一步。??
作为学习者和分享者,我深知总结不易。同时,由于水平有限,文中若有疏漏或错误之处,恳请各位大佬不吝指正。
本教程适用人群:
- 零基础或对 C++ 了解甚少的编程初学者。
- 希望快速掌握 C++ 核心基础语法的学习者。
主要参考资料:
- C++ 教程 | 菜鸟教程 (runoob.com) - 一个非常适合新手学习的在线资源。
2. C++ 初步了解
在正式编写代码之前,让我们先对 C++ 有一个基本的认识。
2.1 C++ 简介
C++ (读作 “C plus plus”) 是一种功能强大、用途广泛的计算机高级程序设计语言。它起源于 C 语言,由丹麦计算机科学家本贾尼·斯特劳斯特卢普 (Bjarne Stroustrup) 于 1979 年在贝尔实验室首次研发成功,最初被称为 “C with Classes” (带类的 C)。
为什么学习 C++
- 性能卓越: C++ 代码编译后接近机器语言,运行效率高,非常适合开发性能要求苛刻的应用,如图形渲染引擎、游戏引擎、操作系统核心、高性能计算等。
- 功能强大: C++ 支持多种编程范式:
- 过程化编程: 像 C 语言一样,按照步骤一步步执行。
- 面向对象编程 (OOP): 将数据和操作数据的方法封装在 “对象” 中,提高了代码的复用性、灵活性和可维护性。这是 C++ 相对于 C 的核心增强。
- 泛型编程: 使用模板(Template)编写与类型无关的代码,提高代码通用性。
- 生态丰富: C++ 拥有庞大的标准库和第三方库,提供了文件操作、网络通信、图形界面、数学计算等丰富的功能支持。
- 应用广泛: 从桌面应用(如 Adobe 系列、Office)、操作系统(Windows、macOS、Linux 部分组件)、数据库(MySQL)、浏览器(Chrome、Firefox),到嵌入式系统、物联网设备,再到大型游戏(Unreal Engine、Unity 部分底层),C++ 的身影无处不在。
虽然 C++ 的语法细节相对较多,学习曲线可能比 Python 等语言陡峭,但掌握它将为你打开通往高性能和系统级编程的大门。可以把它看作是 C 语言的超集,既保留了 C 语言的效率和底层操作能力,又增加了面向对象等现代编程特性。
2.2 我的第一个程序 (“Hello, World!”)
学习任何编程语言,通常都从打印 “Hello, World!” 开始。这能帮助我们验证开发环境是否配置正确,并初步了解语言的基本结构。
#include <iostream> // 1. 包含头文件 iostream
// using namespace std; // 2. 使用 std 命名空间 (这里暂时注释掉,后面解释)// 3. main 函数 - 程序的入口点
int main()
{// 4. 使用 std::cout 输出 "Hello, world!" 到控制台std::cout << "Hello, world!" << std::endl; // endl 表示换行// 5. main 函数返回 0,表示程序正常结束return 0;
}
代码解释:
#include <iostream>
:这是一个预处理指令。它告诉编译器把iostream
这个头文件包含进来。iostream
文件包含了进行标准输入输出操作所需的信息,比如我们要用的std::cout
。// using namespace std;
:这行代码被//
注释掉了。如果取消注释,它表示我们要使用std
这个命名空间 (namespace)。命名空间是为了防止不同库或代码模块之间的命名冲突。cout
和endl
都定义在std
命名空间里。如果使用了这行,后面就可以直接写cout
和endl
。现在我们没用它,所以需要写完整的std::cout
和std::endl
。在大型项目中,通常不推荐全局使用using namespace std;
,但在小程序或教学中为了简洁,有时会使用。int main()
:这是主函数,是每个 C++ 程序执行的起点。操作系统会调用这个函数来运行你的程序。int
表示这个函数执行完毕后会返回一个整数值。括号()
表示这是一个函数。std::cout << "Hello, world!" << std::endl;
:这是执行输出的核心语句。std::cout
:是std
命名空间中的标准输出流对象,通常代表控制台(就是你运行程序时看到的那个黑窗口)。<<
:是输出运算符(也叫插入运算符),它把右边的内容发送给左边的输出流。"Hello, world!"
:是要输出的字符串字面量。std::endl
:是std
命名空间中的一个特殊操纵符,作用是输出一个换行符,并刷新输出缓冲区(确保内容立即显示)。
return 0;
:表示main
函数执行完毕,并向操作系统返回一个值0
。通常,返回0
代表程序正常结束,返回非零值表示程序遇到了错误。
编译和运行:
你需要一个 C++ 编译器来将这段源代码转换成可执行文件。编译运行后,你会在控制台上看到:
Hello, world!
(根据你的编译环境,可能还会显示 “press any key to exit…” 或类似信息)
测试用例 / 动手试试:
- 尝试修改
"Hello, world!"
为其他你想输出的文字,比如"My first C++ program!"
。 - 尝试去掉
std::endl
,看看输出有什么不同。(提示:可能不会自动换行了) - 尝试取消
// using namespace std;
的注释,然后把std::cout
和std::endl
改为cout
和endl
,看看是否能正常编译运行。
2.3 C++ 标准库
想象一下,如果你每次做饭都要从种菜、养鸡开始,那该多麻烦!编程也是如此。C++ 提供了一个强大的标准库 (Standard Library),就像一个巨大的工具箱,里面预先准备好了各种常用的功能,让我们不必事事从头做起。
标准的 C++ 主要由三部分组成:
- 核心语言 (Core Language): 定义了语言的基本语法规则,如变量、数据类型、运算符、控制流语句 (if, for, while 等)、函数等。这是构建一切的基础。
- C++ 标准库 (C++ Standard Library): 提供了大量的预定义类 (Class) 和函数 (Function),极大地扩展了核心语言的能力。它包括:
- 输入/输出 (I/O): 如我们刚刚用到的
iostream
,用于屏幕输出和键盘输入,还有文件读写等。 - 字符串处理: 提供了
string
类,方便地操作文本数据。 - 容器 (Containers): 如
vector
(动态数组),list
(链表),map
(键值对) 等,用于高效地存储和管理数据集合。 - 算法 (Algorithms): 如排序 (
sort
), 查找 (find
), 复制 (copy
) 等常用操作。 - 实用工具: 如数学函数 (
cmath
), 时间处理 (chrono
), 内存管理工具等。
- 输入/输出 (I/O): 如我们刚刚用到的
- 标准模板库 (Standard Template Library, STL): STL 是 C++ 标准库的一个核心组成部分,它主要由容器、算法、迭代器 (Iterators)(用于遍历容器中的元素)和函数对象 (Functors) 构成。STL 的设计基于泛型编程,使得这些组件具有高度的通用性和效率。
简单来说,学习 C++ 不仅仅是学习语法,更是学习如何有效地利用强大的标准库来解决问题。
2.4 编译器及编译环境
我们写的 C++ 代码(.cpp
文件)是人类可读的文本,但计算机 CPU 只能理解机器码(一堆 0 和 1)。编译器 (Compiler) 就是一个特殊的程序,负责将我们的 C++ 源代码翻译成计算机能执行的机器码(生成可执行文件,如 Windows 下的 .exe
文件)。
常见的 C++ 编译器:
- GCC (GNU Compiler Collection): 开源、跨平台,非常流行,尤其在 Linux/macOS 系统中。
g++
是 GCC 中用于编译 C++ 的命令。 - Clang: 较新的开源编译器,以其快速编译和清晰的错误/警告信息而闻名,与 LLVM 项目紧密相关。
- MSVC (Microsoft Visual C++): 微软开发的编译器,集成在 Visual Studio 开发环境中,主要用于 Windows 平台开发。
- MinGW / Cygwin: 在 Windows 平台上提供类 Unix 环境和 GCC 工具链的解决方案,使得可以在 Windows 上使用 GCC 编译。
集成开发环境 (IDE, Integrated Development Environment):
对于初学者来说,直接使用命令行编译器可能有些复杂。IDE 将编写代码(编辑器)、编译代码(编译器)、调试代码(调试器)等功能集成在了一个图形界面应用中,大大提高了开发效率。
推荐给新手的 IDE 或编辑器:
- Visual Studio (Windows): 功能非常强大且全面的 IDE,社区版 (Community Edition) 免费。
- Visual Studio Code (VS Code) (跨平台): 轻量级、高度可扩展的代码编辑器,通过安装 C/C++ 扩展插件 (如 Microsoft 的 C/C++ extension) 并配置编译器(如 MinGW 或 Clang)可以获得良好的 C++ 开发体验。免费。
- Code::Blocks (跨平台): 免费、开源的 C++ IDE,自带 MinGW 编译器(Windows 版本),配置简单,适合初学者。
- Dev-C++ (Windows): 比较老牌的免费 C++ IDE,也是很多学校教学的选择,但更新相对较慢。
选择哪个?
对于 Windows 用户,Visual Studio Community 或 VS Code + MinGW 是不错的选择。对于 macOS/Linux 用户,VS Code + Clang/GCC 是常用组合。选择一个你觉得用起来顺手的即可。最好还是用qt,qt能很简单的交叉编译,且使用简单,qt的很多控件都用c++写。
编译过程简介 (了解即可):
源代码 (.cpp
) -> 预处理器 (处理 #include
, #define
等) -> 编译器 (翻译成汇编代码) -> 汇编器 (转换成目标文件 .o
或 .obj
) -> 链接器 (将你的目标文件和库文件链接起来) -> 可执行文件 (.exe
或无后缀)。IDE 会自动完成这些步骤。
2.5 注释:给代码添加说明
注释是代码中不会被编译器执行的部分,它们是写给人看的,用来解释代码的意图、逻辑或者暂时禁用某段代码。良好的注释习惯对于代码的可读性和可维护性至关重要。
C++ 支持两种类型的注释:
-
单行注释: 以
//
开始,从//
到该行末尾的所有内容都被视为注释。// 这是单行注释,用于解释下面这行代码的作用 std::cout << "Hello!" << std::endl; // 也可以把注释放在代码行的末尾
-
多行注释: 以
/*
开始,以*/
结束,可以跨越多行。/* 这是多行注释。 可以写很多行的说明文字。 这部分内容都不会被编译。 int x = 10; // 这行代码在注释内部,也不会执行 */ int y = 20; // 这行代码在注释外部,会执行
为什么要写注释?
- 解释复杂逻辑: 当代码实现比较巧妙或复杂时,注释可以帮助他人(以及未来的你)理解。
- 说明代码意图: 解释这段代码“为什么”要这么写,它的目的是什么。
- 标记待办事项: 如
// TODO: 添加错误处理
。 - 临时禁用代码: 在调试时,可以暂时注释掉一部分代码,而不是直接删除。
注意: 不要过度注释。对于显而易见的代码(如 i++; // i 增加 1
),注释是多余的。好的变量命名和代码结构本身就应该具有一定的自解释性。
3. C++ 数据类型
计算机程序的核心是处理数据。数据类型 (Data Type) 告诉编译器如何解释内存中的二进制数据,以及可以对这些数据执行哪些操作。例如,int
类型告诉编译器这块内存存的是一个整数,可以进行加减乘除等运算。
3.1 基本数据类型
C++ 提供了一组内置的基本数据类型,也称为原生数据类型 (Primitive Data Types)。以下是初学者最需要掌握的几种:
类型 | 关键字 | 描述 | 典型示例 |
---|---|---|---|
整型 | int | 用于存储整数 (没有小数部分) | 10 , -5 , 0 |
浮点型 | float | 用于存储单精度浮点数 (带小数点的数) | 3.14f , -0.5f |
双精度型 | double | 用于存储双精度浮点数 (比 float 精度更高) | 3.14159 , -2.7 |
字符型 | char | 用于存储单个字符 (字母、数字、符号) | 'A' , '1' , '$' |
布尔型 | bool | 用于存储逻辑值,只有 true (真) 或 false (假) | true , false |
无类型 | void | 特殊类型,表示“无类型”,常用于函数返回值或指针 | (不直接存储值) |
补充说明:
- 浮点数后缀:
float
类型的字面量通常建议加上f
或F
后缀,以区别于默认为double
的浮点数字面量。 - 字符表示:
char
类型的值用单引号' '
括起来。 - 宽字符型 (
wchar_t
): 用于存储支持更广泛字符集(如 Unicode)的字符,对于初学者来说,除非有特定需求(如处理多语言文本),否则可以暂时不必深入了解。
类型修饰符:
基本整型 (int
, char
) 和浮点型 (double
) 可以使用一些修饰符 (Modifiers) 来改变它们的特性(如存储范围或符号):
signed
:表示可以存储正数、负数和零(对于int
和char
是默认的)。unsigned
:表示只能存储非负数(零和正数),但可以存储更大的正数。short
:通常使int
占用的内存更少。long
:通常使int
占用的内存更多,存储范围更大。double
也可以用long
修饰 (long double
),提供更高精度。
组合示例:
unsigned int
:无符号整数。short int
(或简称short
):短整型。long int
(或简称long
):长整型。long long int
(或简称long long
):更长的整型 (C++11 标准引入)。unsigned long long
:无符号长长整型。long double
:长双精度浮点型。
初学阶段,掌握 int
, float
, double
, char
, bool
通常就足够了。
3.2 数据类型占用空间大小
不同的数据类型在内存中占用的字节数 (Bytes) 不同,这决定了它们能够表示的数值范围。注意: C++ 标准并未严格规定每种类型的确切大小,只规定了它们之间的相对大小关系(例如,sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
)。实际大小可能因操作系统和编译器而异。
以下是一个典型的 32 位或 64 位系统上的大小和范围示例(仅供参考):
类型 | 大小 (字节) | 大致范围 / 精度 |
---|---|---|
char | 1 | -128 到 127 或 0 到 255 (取决于是否默认为 signed) |
unsigned char | 1 | 0 到 255 |
signed char | 1 | -128 到 127 |
short | 2 | -32,768 到 32,767 |
unsigned short | 2 | 0 到 65,535 |
int | 4 | -2,147,483,648 到 2,147,483,647 |
unsigned int | 4 | 0 到 4,294,967,295 |
long | 4 或 8 | (依赖系统) |
unsigned long | 4 或 8 | (依赖系统) |
long long | 8 | 约 -9 x 10^18 到 9 x 10^18 |
unsigned long long | 8 | 0 到 约 1.8 x 10^19 |
float | 4 | 约 +/- 3.4 x 10^38 (大约 6-7 位十进制有效数字) |
double | 8 | 约 +/- 1.7 x 10^308 (大约 15-16 位十进制有效数字) |
long double | 10, 12 或 16 | (依赖系统,更高精度) |
bool | 1 (通常) | true , false |
如何查看特定类型的大小?
可以使用 sizeof
运算符来获取某个类型或变量在当前系统上占用的字节数。
测试用例 / 动手试试:
#include <iostream>int main() {std::cout << "Size of char: " << sizeof(char) << " bytes" << std::endl;std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;std::cout << "Size of float: " << sizeof(float) << " bytes" << std::endl;std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;std::cout << "Size of bool: " << sizeof(bool) << " bytes" << std::endl;int myVariable = 10;std::cout << "Size of myVariable (int): " << sizeof(myVariable) << " bytes" << std::endl;return 0;
}
编译运行这段代码,看看在你自己的系统上,这些类型各占多少字节。
3.3 变量:存储数据的容器
变量 (Variable) 可以看作是内存中一块存储区域的名字。通过这个名字,我们可以访问和修改这块区域中存储的数据。在使用变量之前,必须先声明 (Declare) 它,告诉编译器这个变量的名字和它要存储的数据类型。
变量声明语法:
DataType variableName;
例如:
int age; // 声明一个名为 age 的整型变量
double salary; // 声明一个名为 salary 的双精度浮点型变量
char grade; // 声明一个名为 grade 的字符型变量
bool isStudent; // 声明一个名为 isStudent 的布尔型变量
变量定义 (Definition) / 初始化 (Initialization):
声明只是告诉编译器有这样一个变量,但它里面存储的是什么值是不确定的(可能是随机的垃圾值)。通常,我们在声明变量的同时给它一个初始值,这个过程称为初始化。包含初始化的声明也称为定义。
DataType variableName = initialValue; // C 风格初始化
DataType variableName(initialValue); // 构造函数风格初始化 (不常用)
DataType variableName{initialValue}; // C++11 统一初始化 (推荐)
示例:
int age = 25; // 声明并初始化 age 为 25
double salary = 5000.50; // 声明并初始化 salary
char grade = 'A'; // 声明并初始化 grade
bool isStudent = true; // 声明并初始化 isStudent// C++11 统一初始化 (更安全,能防止某些类型窄化)
int score{95};
double pi{3.14159};
char initial{'J'};
bool completed{false};
变量命名规则与约定:
- 名称只能包含字母、数字和下划线
_
。 - 名称必须以字母或下划线开头(以下划线开头通常保留给系统或库使用,建议用字母开头)。
- 名称不能是 C++ 的关键字(如
int
,if
,for
,class
等)。 - 名称是大小写敏感的(
age
和Age
是两个不同的变量)。 - 约定:
- 驼峰命名法 (CamelCase): 第一个单词小写,后续单词首字母大写,如
myVariableName
。 - 帕斯卡命名法 (PascalCase): 每个单词首字母都大写,常用于类名,如
MyClass
。 - 下划线命名法 (snake_case): 单词之间用下划线分隔,全小写,如
my_variable_name
。
选择一种风格并保持一致。驼峰命名法和下划线命名法在 C++ 中都很常见。
- 驼峰命名法 (CamelCase): 第一个单词小写,后续单词首字母大写,如
变量的使用:
#include <iostream>int main() {int width = 10; // 定义并初始化宽度int height{5}; // 定义并初始化高度 (C++11 风格)int area = width * height; // 使用变量计算面积,并将结果存储到新变量 area 中std::cout << "Width: " << width << std::endl;std::cout << "Height: " << height << std::endl;std::cout << "Area: " << area << std::endl;// 修改变量的值width = 12;area = width * height; // 需要重新计算std::cout << "New Width: " << width << std::endl;std::cout << "New Area: " << area << std::endl;return 0;
}
测试用例 / 动手试试:
- 声明一个
float
类型的变量price
并初始化为19.99f
。 - 声明一个
int
类型的变量quantity
并初始化为3
。 - 计算总价
totalPrice = price * quantity
,并声明一个float
或double
变量存储结果。 - 使用
std::cout
输出price
,quantity
, 和totalPrice
的值。
3.4 常量:不可改变的值
常量 (Constant) 是一种特殊的“变量”,它的值在程序运行期间不能被修改。使用常量可以提高代码的可读性和可维护性,并防止意外修改重要的值。
例如,数学中的 π (约 3.14159) 是一个常数,如果在程序中多处用到它,定义成常量会更方便且不易出错。
在 C++ 中,定义常量主要有两种方式:
-
使用
const
关键字 (推荐): 这是 C++ 中定义常量的首选方式。const DataType constantName = value;
示例:
const double PI = 3.14159; const int MAX_USERS = 100; const char NEWLINE = '\n'; const bool DEBUG_MODE = false;// PI = 3.14; // 错误!编译器会报错,因为 PI 是常量,不能被修改
优点:
- 有明确的数据类型,编译器会进行类型检查。
- 有作用域(通常在定义它的代码块内有效)。
- 可以用于更复杂的类型(如常量对象、常量指针等)。
-
使用
#define
预处理器指令 (不推荐用于定义常量): 这是从 C 语言继承来的方式。#define CONSTANT_NAME value
示例:
#define PI 3.14159 #define MAX_USERS 100
工作方式: 预处理器在编译之前,会把代码中所有出现的
CONSTANT_NAME
简单地文本替换为value
。缺点:
- 没有类型信息,只是简单的文本替换,可能导致意想不到的错误,且不利于调试。
- 没有作用域的概念,通常是全局替换。
- 不符合 C++ 的类型安全理念。
结论: 在现代 C++ 中,请优先使用 const
来定义常量。#define
更多地用于定义宏(虽然宏也需谨慎使用)或头文件保护。
常量命名约定: 通常使用全大写字母,单词间用下划线分隔,以清晰地表示这是一个常量,例如 MAX_BUFFER_SIZE
。
测试用例 / 动手试试:
- 使用
const
定义一个表示一年中月份数量的整型常量MONTHS_IN_YEAR
,值为 12。 - 尝试在定义后修改它的值,看看编译器会报什么错误。
- 定义一个
double
类型的变量radius = 5.0
。 - 使用你定义的
PI
常量 (用const
定义的) 和radius
变量计算圆的面积area = PI * radius * radius
。 - 输出
area
的值。
4. 运算符:执行操作的符号
运算符 (Operator) 是用于执行特定操作(如数学计算、比较、逻辑判断等)的特殊符号。C++ 内置了丰富的运算符。初学者需要掌握以下几类:
4.1 算术运算符
用于执行基本的数学运算。假设 int A = 10;
int B = 20;
double C = 7.0;
double D = 2.0;
运算符 | 描述 | 示例 (整型) | 示例 (浮点型) | 注意点 |
---|---|---|---|---|
+ | 加法 | A + B 结果是 30 | C + D 结果是 9.0 | |
- | 减法 | B - A 结果是 10 | C - D 结果是 5.0 | |
* | 乘法 | A * B 结果是 200 | C * D 结果是 14.0 | |
/ | 除法 | B / A 结果是 2 | C / D 结果是 3.5 | 整数除法会舍弃小数部分! 10 / 3 结果是 3 ,不是 3.33 。 |
% | 取模 (求余数) | B % A 结果是 0 | (通常不用于浮点数) | 只能用于整数。10 % 3 结果是 1 。 |
++ | 自增 | A++ (后置) 或 ++A (前置) | (同左) | 使变量的值增加 1。++A 先加后用,A++ 先用后加。 |
-- | 自减 | A-- (后置) 或 --A (前置) | (同左) | 使变量的值减少 1。--A 先减后用,A-- 先用后减。 |
测试用例 / 动手试试:
#include <iostream>int main() {int x = 10;int y = 3;double f1 = 10.0;double f2 = 3.0;std::cout << "x / y = " << (x / y) << std::endl; // 整数除法std::cout << "f1 / f2 = " << (f1 / f2) << std::endl; // 浮点数除法std::cout << "f1 / y = " << (f1 / y) << std::endl; // 混合类型除法 (结果为 double)std::cout << "x % y = " << (x % y) << std::endl; // 取模int counter = 5;std::cout << "Counter (pre-increment): " << ++counter << std::endl; // 先加1,再输出 (6)std::cout << "Counter (now): " << counter << std::endl; // 值为 6std::cout << "Counter (post-increment): " << counter++ << std::endl;// 先输出 (6),再加1std::cout << "Counter (finally): " << counter << std::endl; // 值为 7return 0;
}
4.2 关系运算符
用于比较两个值的大小关系,结果是一个布尔值 (true
或 false
)。假设 int A = 10;
int B = 20;
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
== | 等于 | (A == B) | false |
!= | 不等于 | (A != B) | true |
> | 大于 | (A > B) | false |
< | 小于 | (A < B) | true |
>= | 大于或等于 | (A >= B) | false |
<= | 小于或等于 | (A <= B) | true |
关系运算符常用于 if
语句或循环的条件判断中。
测试用例 / 动手试试:
#include <iostream>int main() {int score = 85;bool isPass = (score >= 60); // 判断是否及格bool isExcellent = (score == 100); // 判断是否满分std::cout << "Score: " << score << std::endl;std::cout << "Passed? " << std::boolalpha << isPass << std::endl; // std::boolalpha 让布尔值输出 true/false 而不是 1/0std::cout << "Excellent? " << std::boolalpha << isExcellent << std::endl;return 0;
}
4.3 逻辑运算符
用于组合或修改布尔表达式的结果,结果也是一个布尔值 (true
或 false
)。假设 bool conditionA = true;
bool conditionB = false;
运算符 | 描述 | 名称 | 示例 | 结果 | 说明 |
---|---|---|---|---|---|
&& | 逻辑与 | AND | (conditionA && conditionB) | false | 只有都为 true 时,结果才为 true 。 |
` | ` | 逻辑或 | OR | `(conditionA | |
! | 逻辑非 | NOT | !conditionA | false | 将 true 变为 false ,将 false 变为 true (取反)。 |
短路求值 (Short-circuit Evaluation):
- 对于
&&
:如果左边的表达式为false
,则右边的表达式不会被计算(因为结果肯定为false
)。 - 对于
||
:如果左边的表达式为true
,则右边的表达式不会被计算(因为结果肯定为true
)。
这在某些情况下可以提高效率或避免错误(如先检查指针是否为空再访问其成员)。
测试用例 / 动手试试:
#include <iostream>int main() {int age = 25;bool hasLicense = true;bool canRentCar = (age >= 21) && hasLicense; // 年龄符合且有驾照bool isMinorOrNoLicense = (age < 18) || !hasLicense; // 未成年或无驾照std::cout << "Age: " << age << ", Has License: " << std::boolalpha << hasLicense << std::endl;std::cout << "Can Rent Car? " << std::boolalpha << canRentCar << std::endl;std::cout << "Is Minor or No License? " << std::boolalpha << isMinorOrNoLicense << std::endl;return 0;
}
4.4 位运算符 (进阶)
位运算符直接操作变量在内存中的二进制位 (bits)。它们通常用于底层编程、性能优化、硬件交互或某些特定算法中。初学者可以先做了解,不必立即深入掌握。
假设 unsigned char A = 60;
(二进制 0011 1100
), unsigned char B = 13;
(二进制 0000 1101
)
运算符 | 描述 | 名称 | 示例 | 结果 (十进制) | 结果 (二进制) | 说明 (按位计算) |
---|---|---|---|---|---|---|
& | 按位与 | AND | (A & B) | 12 | 0000 1100 | 对应位都为 1 时,结果位为 1,否则为 0。 |
` | ` | 按位或 | OR | `(A | B)` | 61 |
^ | 按位异或 | XOR | (A ^ B) | 49 | 0011 0001 | 对应位相同时,结果位为 0,不同时为 1。 |
~ | 按位取反 | NOT | (~A) | (依赖类型大小) | 1100 0011 | 将每一位取反 (0 变 1, 1 变 0)。结果解释依赖于类型和符号位。 |
<< | 左移 | Shift Left | (A << 2) | 240 | 1111 0000 | 所有位向左移动指定位数,右边空出的位补 0。相当于乘以 2 的幂次。 |
>> | 右移 | Shift Right | (A >> 2) | 15 | 0000 1111 | 所有位向右移动指定位数。对于无符号数,左边补 0;对于有符号数,行为可能依赖实现(通常补符号位)。相当于除以 2 的幂次。 |
应用场景举例:
- 设置/清除/检查特定标志位。
- 快速乘以或除以 2 的幂次方。
- 颜色值的组合与分解 (如 ARGB)。
- 加密算法。
5. 头文件:代码的“说明书”
想象一下你要使用一个别人写好的工具(比如一个函数或一个类),你需要知道这个工具叫什么名字、怎么用(需要提供什么参数、会返回什么结果)。头文件 (Header File),通常以 .h
或 .hpp
为后缀(C++ 标准库头文件通常没有后缀,如 iostream
, string
, vector
),就扮演了这种“说明书”的角色。
头文件的主要作用:
- 声明 (Declaration): 头文件包含了函数、类、变量、常量等的声明。声明告诉编译器这些东西的存在以及它们的接口(名称、参数类型、返回类型等),但通常不包含具体的实现细节(函数体、类成员函数的具体代码)。
- 共享接口: 当你的项目包含多个源文件 (
.cpp
) 时,可以将共享的函数声明、类定义等放在头文件中,然后在需要使用这些功能的源文件中通过#include
包含该头文件。这样可以避免重复声明,并确保接口一致。 - 引入库功能: 使用标准库或第三方库的功能时,你需要包含相应的头文件,以便编译器知道这些库提供的函数和类的存在和用法。例如,要使用
cout
,必须包含<iostream>
。
如何包含头文件?
使用预处理指令 #include
:
#include <header_name> // 用于包含标准库头文件或系统头文件 (编译器会在标准路径下查找)
#include "my_header.h" // 用于包含用户自己编写的头文件 (编译器通常会先在当前目录下查找)
示例:
#include <iostream> // 包含标准输入输出流库的头文件
#include <string> // 包含标准字符串类的头文件
#include <vector> // 包含标准动态数组 (vector) 类的头文件
#include "my_functions.h" // 包含你自己写的名为 my_functions.h 的头文件
总结: 头文件是 C++ 模块化编程的基础,它定义了代码模块之间的接口。当你 #include
一个头文件时,相当于把头文件的内容复制粘贴到了当前文件的那个位置(在预处理阶段完成)。
6. 输入输出流:与程序交互
程序需要与外界交互才能发挥作用。最常见的交互方式就是从键盘接收输入 (Input) 和向屏幕显示输出 (Output)。C++ 通过流 (Stream) 的概念来处理输入输出。流可以看作是数据传输的通道。
核心概念:
- 输入流 (Input Stream): 数据从输入设备(如键盘、文件)流向程序。标准输入流是
std::cin
。 - 输出流 (Output Stream): 数据从程序流向输出设备(如屏幕、文件)。标准输出流是
std::cout
,标准错误流是std::cerr
(通常也输出到屏幕,用于显示错误信息)。
要使用 std::cin
和 std::cout
,必须包含 <iostream>
头文件。
输出 (std::cout
)
我们已经用过 std::cout
了。使用输出运算符 <<
(插入运算符)将数据发送到输出流。
#include <iostream>
#include <string> // 为了使用 std::stringint main() {int age = 30;double temperature = 98.6;std::string name = "Alice";std::cout << "Hello, " << name << "!" << std::endl; // 输出字符串和变量std::cout << "Your age is: " << age << std::endl;std::cout << "Current temperature: " << temperature << " F" << std::endl;std::cout << "This is line 1." << std::endl << "This is line 2." << std::endl; // 可以连续输出和换行return 0;
}
输入 (std::cin
)
使用输入运算符 >>
(提取运算符)从输入流(通常是键盘)读取数据,并存储到变量中。
#include <iostream>
#include <string> // 为了使用 std::stringint main() {int userAge;double userHeight;std::string userName;std::cout << "Please enter your name: ";std::cin >> userName; // 从键盘读取一个单词(遇到空白符停止)存入 userNamestd::cout << "Please enter your age: ";std::cin >> userAge; // 从键盘读取一个整数存入 userAgestd::cout << "Please enter your height (in meters): ";std::cin >> userHeight; // 从键盘读取一个浮点数存入 userHeightstd::cout << "\n--- User Information ---" << std::endl; // 输出一个空行和标题std::cout << "Name: " << userName << std::endl;std::cout << "Age: " << userAge << " years old" << std::endl;std::cout << "Height: " << userHeight << " meters" << std::endl;return 0;
}
注意: std::cin >> variable;
默认会以空白字符(空格、制表符、换行符)作为分隔符。如果你输入 “John Doe”,std::cin >> userName;
只会读取 “John” 到 userName
中。如果需要读取一整行(包含空格),需要使用 std::getline(std::cin, variable);
函数。
测试用例 / 动手试试:
编写一个程序:
- 提示用户输入两个整数。
- 使用
std::cin
读取这两个整数,分别存入两个int
变量中。 - 计算这两个整数的和。
- 使用
std::cout
输出类似 “The sum of X and Y is Z” 的结果。
7. 分支语句:让程序做出选择
现实世界中,我们经常需要根据不同的情况做出不同的决定。程序也一样。分支语句 (Branching Statements) 允许程序根据一个或多个条件 (Condition) 的真假来选择执行不同的代码路径。
7.1 if-else 语句
if-else
是最常用的分支结构。
基本形式 1: if
如果条件为真,则执行 if
块内的代码。
if (condition) {// 如果 condition 为 true,执行这里的代码statement(s);
}
// 程序继续执行后续代码
流程图 (Mermaid 语法):
基本形式 2: if-else
如果条件为真,执行 if
块;否则(条件为假),执行 else
块。
if (condition) {// 如果 condition 为 true,执行这里的代码statement(s)_if_true;
} else {// 如果 condition 为 false,执行这里的代码statement(s)_if_false;
}
// 程序继续执行后续代码
流程图 (Mermaid 语法):
基本形式 3: if-else if-else
用于检查多个互斥的条件。程序会从上到下依次检查条件,一旦找到一个为真的条件,就执行对应的代码块,然后跳过剩余的 else if
和 else
。如果所有 if
和 else if
的条件都为假,则执行最后的 else
块(else
块是可选的)。
if (condition1) {// 如果 condition1 为 true,执行这里的代码statement(s)_1;
} else if (condition2) {// 如果 condition1 为 false 且 condition2 为 true,执行这里的代码statement(s)_2;
} else if (condition3) {// 如果 condition1, 2 为 false 且 condition3 为 true,执行这里的代码statement(s)_3;
} else {// 如果以上所有条件都为 false,执行这里的代码 (可选)statement(s)_else;
}
// 程序继续执行后续代码
流程图 (Mermaid 语法):
graph TDA[开始] --> B{条件1 是否为真?};B -- True --> C[执行代码块 1];B -- False --> D{条件2 是否为真?};D -- True --> E[执行代码块 2];D -- False --> F{条件3 是否为真?};F -- True --> G[执行代码块 3];F -- False --> H[执行 else 代码块 (可选)];C --> Z[程序继续];E --> Z;G --> Z;H --> Z;
示例:判断分数等级
#include <iostream>int main() {int score;std::cout << "Enter your score (0-100): ";std::cin >> score;char grade;if (score >= 90 && score <= 100) {grade = 'A';} else if (score >= 80 && score < 90) {grade = 'B';} else if (score >= 70 && score < 80) {grade = 'C';} else if (score >= 60 && score < 70) {grade = 'D';} else if (score >= 0 && score < 60) {grade = 'F';} else {std::cout << "Invalid score entered." << std::endl;return 1; // 返回非 0 表示程序异常结束}// 只有在分数有效时才输出等级if (score >= 0 && score <= 100) {std::cout << "Your grade is: " << grade << std::endl;}return 0;
}
测试用例 / 动手试试:
- 运行上面的分数等级程序,分别输入 95, 82, 70, 60, 45, 105, -10,观察输出结果。
- 编写一个程序,判断用户输入的整数是奇数还是偶数。(提示:使用取模运算符
%
)
7.2 switch 语句
switch
语句提供了一种更清晰的方式来根据一个变量的不同取值执行不同的代码块。它特别适用于检查一个变量是否等于多个常量值中的某一个。
语法:
switch (expression) { // expression 必须是整数类型 (int, char, enum 等)case constant_value_1:// 如果 expression 的值等于 constant_value_1,执行这里的代码statement(s)_1;break; // 非常重要!跳出 switch 语句case constant_value_2:// 如果 expression 的值等于 constant_value_2,执行这里的代码statement(s)_2;break;case constant_value_3:// ... 可以有更多的 casestatement(s)_3;break;default: // 可选// 如果 expression 的值不匹配任何一个 case,执行这里的代码statement(s)_default;// default 后面通常也需要 break,虽然有时省略也无妨break;
}
// 程序在 break 后会跳到这里继续执行
关键点:
expression
:括号里的表达式会被求值,其结果必须是整数类型(包括char
,因为char
本质上也是整数)。case constant_value:
:case
后面必须跟一个常量表达式(如10
,'A'
,const int MY_CONST = 5;
中的MY_CONST
)。不能是变量。break;
:至关重要! 当一个case
的代码执行完毕后,break
语句会使程序跳出整个switch
结构。如果没有break
,程序会继续执行下一个case
的代码(称为“贯穿 (fallthrough)”),这通常不是我们想要的结果,除非是刻意设计的。default:
:是可选的。如果expression
的值与所有case
都不匹配,则执行default
块的代码。
流程图 (Mermaid 语法):
graph TDA[开始] --> B{计算表达式的值};B --> C{值 == case 1?};C -- True --> D[执行 case 1 代码];C -- False --> E{值 == case 2?};D --> X(break);E -- True --> F[执行 case 2 代码];E -- False --> G{...}; // 更多 caseF --> X;G -- False --> H[执行 default 代码 (可选)];H --> X;X --> Z[程序继续];
示例:根据数字输出星期几
#include <iostream>int main() {int day;std::cout << "Enter day number (1-7): ";std::cin >> day;switch (day) {case 1:std::cout << "Monday" << std::endl;break;case 2:std::cout << "Tuesday" << std::endl;break;case 3:std::cout << "Wednesday" << std::endl;break;case 4:std::cout << "Thursday" << std::endl;break;case 5:std::cout << "Friday" << std::endl;break;case 6:std::cout << "Saturday" << std::endl;break;case 7:std::cout << "Sunday" << std::endl;break;default:std::cout << "Invalid day number." << std::endl;break;}return 0;
}
测试用例 / 动手试试:
- 运行上面的星期程序,输入 1 到 7 之间的数字以及其他数字,观察输出。
- 尝试去掉其中一个
case
后面的break;
语句,看看输入那个case
对应的值时会发生什么(贯穿现象)。 - 修改程序,让它接受字符输入 (
'a'
,'b'
,'c'
) 并根据输入输出不同的消息。
if-else
vs switch
:
if-else
更通用,可以处理复杂的条件(范围判断score > 90
,逻辑组合age > 18 && hasLicense
)。switch
主要用于判断一个整数或字符变量是否等于一系列常量值,代码通常更简洁、易读。当if-else if
链条很长且都是==
判断时,可以考虑用switch
替代。
8. 循环:重复执行代码块
有时,我们需要让程序重复执行某段代码多次。例如,打印数字 1 到 100,或者处理一个列表中的所有项目。循环 (Loop) 结构就是用来实现这种重复执行的。
C++ 主要提供三种循环语句:for
, while
, do-while
。
8.1 for 循环
for
循环通常用于已知重复次数或者有清晰的计数器的情况。
语法:
for (initialization; condition; update) {// 循环体 (Loop Body)// 只要 condition 为 true,就执行这里的代码statement(s);
}
// 当 condition 变为 false 时,循环结束,程序跳到这里
组成部分:
initialization
(初始化): 在循环开始前只执行一次。通常用于声明和初始化一个循环控制变量(计数器)。condition
(条件): 每次循环体执行之前都要检查这个条件。如果为true
,则执行循环体;如果为false
,则跳出循环。update
(更新): 每次循环体执行之后执行。通常用于更新循环控制变量(如自增i++
或自减i--
)。
执行流程:
- 执行
initialization
。 - 检查
condition
。 - 如果
condition
为true
:
a. 执行循环体statement(s)
。
b. 执行update
。
c. 回到步骤 2。 - 如果
condition
为false
:
a. 退出循环,执行循环后面的代码。
流程图 (Mermaid 语法):
示例:打印数字 1 到 5
#include <iostream>int main() {std::cout << "Using for loop:" << std::endl;// i 从 1 开始,只要 i 小于等于 5,就执行循环体,每次循环后 i 增加 1for (int i = 1; i <= 5; i++) {std::cout << i << std::endl;}return 0;
}
输出:
Using for loop:
1
2
3
4
5
无限循环 (死循环):
如果 condition
永远为 true
,循环就不会停止。for (;;)
就是一个典型的 for
循环实现的死循环,因为初始化、条件、更新都是空的(条件为空默认为 true
)。
测试用例 / 动手试试:
- 修改上面的示例,让它打印从 10 到 1 的倒计时。 (提示:初始化
i=10
, 条件i>=1
, 更新i--
) - 编写一个
for
循环,计算 1 到 100 之间所有整数的和。
8.2 while 循环
while
循环用于当条件为真时重复执行代码块的情况。它特别适用于不知道具体要循环多少次,只知道循环应该在某个条件不再满足时停止的情况。
语法:
while (condition) {// 循环体 (Loop Body)// 只要 condition 为 true,就执行这里的代码statement(s);// **重要:** 循环体内通常需要包含能最终让 condition 变为 false 的操作
}
// 当 condition 变为 false 时,循环结束,程序跳到这里
执行流程:
- 检查
condition
。 - 如果
condition
为true
:
a. 执行循环体statement(s)
。
b. 回到步骤 1。 - 如果
condition
为false
:
a. 退出循环,执行循环后面的代码。
关键点: while
循环是先判断条件,再执行循环体。如果第一次检查条件就为 false
,循环体一次都不会执行。
流程图 (Mermaid 语法):
graph TDA[开始] --> B{1. 检查 条件};B -- True --> C[2a. 执行 循环体];C --> B; // 回到条件检查B -- False --> D[3. 循环结束, 程序继续];
示例:使用 while
打印数字 1 到 5
#include <iostream>int main() {std::cout << "Using while loop:" << std::endl;int i = 1; // 1. 初始化计数器 (在循环外)while (i <= 5) { // 2. 检查条件std::cout << i << std::endl; // 3a. 执行循环体i++; // 3b. 更新计数器 (在循环体内,非常重要!)}return 0;
}
输出: (与 for
循环示例相同)
Using while loop:
1
2
3
4
5
无限循环 (死循环):
如果 condition
永远为 true
(例如 while (true)
或 while (1)
),或者循环体内没有能让条件变为 false
的操作,就会导致死循环。
测试用例 / 动手试试:
- 编写一个
while
循环,模拟用户输入密码。程序不断提示用户输入密码,直到用户输入正确的密码(比如 “password123”)为止,然后打印 “Login successful!”。 - 使用
while
循环计算用户输入的一系列正数的和,当用户输入 0 或负数时停止输入并输出总和。
8.3 do-while 循环
do-while
循环与 while
循环非常相似,唯一的区别在于它先执行一次循环体,然后再检查条件。这意味着 do-while
循环的循环体至少会执行一次,无论条件最初是真是假。
语法:
do {// 循环体 (Loop Body)// 这里的代码至少会执行一次statement(s);// **重要:** 循环体内通常需要包含能最终让 condition 变为 false 的操作
} while (condition); // 注意:这里有一个分号 ;
// 当 condition 变为 false 时,循环结束,程序跳到这里
执行流程:
- 执行循环体
statement(s)
。 - 检查
condition
。 - 如果
condition
为true
:
a. 回到步骤 1。 - 如果
condition
为false
:
a. 退出循环,执行循环后面的代码。
流程图 (Mermaid 语法):
graph TDA[开始] --> B[1. 执行 循环体];B --> C{2. 检查 条件};C -- True --> B; // 回到循环体C -- False --> D[4. 循环结束, 程序继续];
示例:至少询问一次用户是否继续
#include <iostream>
#include <string>int main() {char choice;do {// 执行一些操作...std::cout << "Performing an action..." << std::endl;// 询问用户是否继续std::cout << "Do you want to perform the action again? (y/n): ";std::cin >> choice;} while (choice == 'y' || choice == 'Y'); // 如果用户输入 'y' 或 'Y',则继续循环std::cout << "Exiting program." << std::endl;return 0;
}
这个程序会先执行一次操作,然后问用户是否重复。即使用户第一次就输入 ‘n’,操作也已经被执行了一次。
测试用例 / 动手试试:
- 运行上面的示例,分别在第一次提示时输入 ‘y’ 和 ‘n’,观察行为。
- 修改上面的示例,让它在循环内部生成一个 1 到 10 的随机数,并打印出来,然后询问用户是否再生成一个。
何时使用 do-while
? 当你需要确保循环体中的代码至少执行一次时,do-while
是合适的选择,例如,显示菜单并获取用户选项,至少要显示一次菜单。
8.4 嵌套循环
可以在一个循环(外部循环)的内部再包含另一个循环(内部循环)。这称为嵌套循环 (Nested Loops)。
示例:打印 3x3 的星号矩阵
#include <iostream>int main() {int rows = 3;int cols = 3;// 外层循环控制行for (int i = 1; i <= rows; i++) {// 内层循环控制列for (int j = 1; j <= cols; j++) {std::cout << "* "; // 打印星号和空格}// 内层循环结束后,换行,准备打印下一行std::cout << std::endl;}return 0;
}
输出:
* * *
* * *
* * *
执行过程:
- 外层循环
i
从 1 开始。 - 进入内层循环,
j
从 1 循环到 3,打印* * *
。 - 内层循环结束,外层循环打印一个换行符
endl
。 - 外层循环
i
变为 2。 - 再次进入内层循环,
j
从 1 循环到 3,打印* * *
。 - … 以此类推,直到外层循环
i
> 3 结束。
可以嵌套 for
, while
, do-while
的任意组合。嵌套循环常用于处理二维数据(如矩阵、表格)或生成特定模式。
测试用例 / 动手试试:
- 修改上面的示例,打印一个九九乘法表。 (提示:内层循环的条件可能与外层循环变量有关
j <= i
) - 使用嵌套
while
循环实现相同的功能。
8.5 循环控制语句 (break, continue)
有时我们需要在循环正常结束条件满足之前就改变循环的执行流程。C++ 提供了两个主要的循环控制语句:
-
break
语句:- 作用:立即终止包含它的最内层循环(
for
,while
,do-while
)或switch
语句的执行。 - 程序流程将跳转到循环或
switch
语句之后的第一条语句。
示例:找到第一个能被 7 整除的数
#include <iostream>int main() {int foundNumber = -1; // 初始化为未找到for (int i = 1; i <= 100; i++) {if (i % 7 == 0) {foundNumber = i;std::cout << "Found the first number divisible by 7: " << foundNumber << std::endl;break; // 找到后就跳出 for 循环}}// 程序会跳到这里if (foundNumber == -1) {std::cout << "No number divisible by 7 found between 1 and 100." << std::endl;}return 0; }
- 作用:立即终止包含它的最内层循环(
-
continue
语句:- 作用:跳过当前循环迭代中
continue
语句之后的剩余代码,并立即开始下一次迭代。 - 对于
for
循环,会先执行更新部分 (update
),再检查条件。 - 对于
while
和do-while
循环,会直接跳转到条件检查部分。
示例:只打印 1 到 10 之间的奇数
#include <iostream>int main() {std::cout << "Odd numbers between 1 and 10:" << std::endl;for (int i = 1; i <= 10; i++) {if (i % 2 == 0) { // 如果 i 是偶数continue; // 跳过本次迭代的剩余部分 (即下面的 cout)}// 只有当 i 是奇数时,才会执行到这里std::cout << i << std::endl;}return 0; }
- 作用:跳过当前循环迭代中
goto
语句 (不推荐使用):
C++ 也有 goto
语句,可以无条件地将控制转移到程序中带有特定标签 (label:
) 的位置。但是,滥用 goto
会使代码逻辑混乱,难以理解和维护(产生所谓的“面条代码”)。强烈建议初学者避免使用 goto
语句,因为 if
, switch
, 循环和函数通常能提供更结构化、更清晰的控制流。
9. 数组:存储同类型数据的集合
如果需要存储一组相同类型的数据(例如,存储 5 个学生的成绩,或者 10 个整数),一个个地声明变量会非常繁琐(score1
, score2
, …)。数组 (Array) 提供了一种方便的方式来存储固定大小的、同类型元素的有序集合。
数组定义:
DataType arrayName[arraySize];
DataType
:数组中存储的元素的数据类型(如int
,float
,char
)。arrayName
:数组的名称。arraySize
:数组可以容纳的元素个数,必须是一个常量表达式(在编译时就能确定其值)。
示例:
int scores[5]; // 定义一个可以存储 5 个 int 类型元素的数组,名为 scores
double temperatures[10]; // 定义一个可以存储 10 个 double 类型元素的数组
char grades[3]; // 定义一个可以存储 3 个 char 类型元素的数组
数组索引 (Index):
数组中的每个元素都有一个唯一的索引(也叫下标),用于访问该元素。数组索引从 0 开始!对于一个大小为 N
的数组 arr
,其有效的索引范围是 0
到 N-1
。
scores[0]
:访问scores
数组的第一个元素。scores[1]
:访问scores
数组的第二个元素。- …
scores[4]
:访问scores
数组的第五个(最后一个)元素。
重要警告: 访问数组时,索引不能超出有效范围(0
到 arraySize - 1
)。访问 scores[5]
或 scores[-1]
会导致数组越界 (Array Out of Bounds),这是 C++ 中一个非常常见且危险的错误,可能导致程序崩溃或产生不可预测的行为。C++ 不会自动检查数组越界!
数组初始化:
可以在定义数组时为其元素提供初始值。
// 方法 1:提供所有元素的初始值
int numbers[5] = {10, 20, 30, 40, 50}; // numbers[0]=10, numbers[1]=20, ..., numbers[4]=50// 方法 2:提供部分元素的初始值 (其余元素会被自动初始化为 0 或对应类型的零值)
int counts[5] = {1, 2, 3}; // counts[0]=1, counts[1]=2, counts[2]=3, counts[3]=0, counts[4]=0// 方法 3:不指定大小,根据初始化列表自动推断大小
int values[] = {5, 8, 12, 6}; // 编译器会自动推断 values 的大小为 4// 方法 4:使用 C++11 统一初始化语法
double rates[3]{0.5, 0.8, 0.2};
访问和修改数组元素:
使用数组名和索引来访问或修改特定元素的值。
#include <iostream>int main() {int data[4]; // 定义一个大小为 4 的 int 数组// 使用索引赋值data[0] = 100;data[1] = 200;data[2] = data[0] + data[1]; // 使用数组元素进行计算data[3] = 400;// data[4] = 500; // 错误!数组越界!// 使用索引访问并输出std::cout << "Element at index 1: " << data[1] << std::endl;std::cout << "Element at index 2: " << data[2] << std::endl;// 使用循环遍历数组std::cout << "All elements:" << std::endl;for (int i = 0; i < 4; i++) { // 注意循环条件 i < 4 (索引 0, 1, 2, 3)std::cout << "data[" << i << "] = " << data[i] << std::endl;}return 0;
}
测试用例 / 动手试试:
- 定义一个
double
类型的数组prices
,大小为 3,并初始化为{9.99, 15.50, 7.25}
。 - 修改第二个元素 (
prices[1]
) 的值为16.00
。 - 使用
for
循环计算数组中所有价格的总和,并输出结果。
数组的局限性:
- 大小固定: 数组的大小在定义时就确定了,不能在程序运行时改变。如果需要存储可变数量的元素,需要使用更高级的数据结构,如
std::vector
。 - 类型单一: 数组中所有元素必须是相同的数据类型。
10. 字符串:处理文本数据
在 C++ 中处理文本(字符串)主要有两种方式:
- C 风格字符串 (C-style String): 继承自 C 语言,本质上是字符数组 (
char[]
),并以一个特殊的空字符 (\0
) 结尾。 - C++
std::string
类: C++ 标准库提供的字符串类 (std::string
),更安全、更方便、功能更强大。强烈推荐在 C++ 程序中使用std::string
。
10.1 C 风格字符串 (cstring)
C 风格字符串就是一个以空字符 \0
(其 ASCII 值为 0) 作为结束标记的 char
数组。
定义方式:
// 方法 1:使用字符数组初始化 (需要手动添加 '\0')
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};// 方法 2:使用字符串字面量初始化 (编译器会自动在末尾添加 '\0')
char str2[] = "Hello"; // 编译器自动分配大小为 6 (5 个字符 + 1 个 '\0')
char str3[10] = "Hello"; // 分配大小为 10,"Hello" 后面是 '\0' 和未使用的空间
使用 <cstring>
头文件:
C++ 提供了 <cstring>
(或 C 的 <string.h>
) 头文件,包含了一系列操作 C 风格字符串的函数。
函数 | 目的 | 示例 (假设 char s1[20] = "Hello"; char s2[] = " World"; ) | 注意事项 |
---|---|---|---|
strcpy(dest, src) | 复制 src 字符串到 dest | strcpy(s1, s2); (结果: s1 变为 " World") | 极其危险! 如果 dest 空间不足,会导致缓冲区溢出。 |
strcat(dest, src) | 将 src 字符串连接到 dest 字符串的末尾 | strcat(s1, s2); (结果: s1 变为 “Hello World”) | 极其危险! 如果 dest 空间不足,会导致缓冲区溢出。 |
strlen(str) | 返回字符串 str 的长度(不包括末尾的 \0 ) | strlen(s1); (结果: 5) | |
strcmp(s1, s2) | 比较两个字符串 s1 和 s2 | strcmp(s1, "Hello"); (结果: 0) | 返回 0 表示相等;< 0 表示 s1 < s2 ;> 0 表示 s1 > s2 。 |
strchr(str, ch) | 在 str 中查找字符 ch 的首次出现位置 | strchr(s1, 'l'); (返回指向第一个 ‘l’ 的指针) | 找不到返回 nullptr 。 |
strstr(s1, s2) | 在 s1 中查找子字符串 s2 的首次出现位置 | strstr(s1, "ell"); (返回指向 “ell” 起始处的指针) | 找不到返回 nullptr 。 |
C 风格字符串的缺点:
- 大小固定: 字符数组大小固定,难以处理长度可变的文本。
- 手动管理
\0
: 很多操作需要确保\0
的存在。 - 不安全: 像
strcpy
,strcat
这样的函数很容易导致缓冲区溢出,这是严重的安全漏洞。需要使用更安全的版本如strncpy
,strncat
,但使用起来更复杂。 - 操作不便: 比较、连接、查找等操作都需要调用函数,不如
std::string
的运算符和成员函数直观。
结论: 了解 C 风格字符串是为了理解一些底层机制和兼容旧代码,但在新的 C++ 代码中,应尽量避免直接使用它们进行复杂的字符串操作,优先选择 std::string
。
10.2 C++ string 类
std::string
是 C++ 标准库 <string>
头文件中定义的一个类,专门用于方便、安全地处理字符串。
优点:
- 动态大小: 自动管理内存,可以根据需要增长或缩小。
- 安全性高: 不容易发生缓冲区溢出。
- 操作方便: 支持用
+
进行连接,用==
,!=
,<
,>
等直接比较,提供了丰富的成员函数(如length()
,size()
,find()
,substr()
等)。 - 与 C++ 标准库(如容器、算法)集成良好。
使用方法:
首先需要包含头文件:#include <string>
#include <iostream>
#include <string> // 必须包含int main() {// 创建 string 对象std::string greeting = "Hello"; // 从 C 风格字符串字面量创建std::string name = "Alice";std::string message; // 创建一个空字符串// 字符串连接 (+)message = greeting + ", " + name + "!"; // 结果: "Hello, Alice!"std::cout << message << std::endl;// 获取长度std::cout << "Message length: " << message.length() << std::endl; // length() 和 size() 功能相同std::cout << "Message size: " << message.size() << std::endl;// 比较字符串 (==, !=, <, >, <=, >=)std::string str1 = "apple";std::string str2 = "banana";if (str1 < str2) { // 按字典序比较std::cout << str1 << " comes before " << str2 << std::endl;}// 访问单个字符 (类似数组,使用索引,从 0 开始)std::cout << "First character of message: " << message[0] << std::endl; // 输出 'H'message[0] = 'J'; // 修改第一个字符std::cout << "Modified message: " << message << std::endl; // 输出 "Jello, Alice!"// 查找子串 (find)size_t pos = message.find("Alice"); // find 返回子串首次出现的索引,找不到返回 std::string::nposif (pos != std::string::npos) { // std::string::npos 是一个特殊值,表示“未找到”std::cout << "\"Alice\" found at index: " << pos << std::endl;} else {std::cout << "\"Alice\" not found." << std::endl;}// 获取子串 (substr)std::string sub = message.substr(7, 5); // 从索引 7 开始,提取 5 个字符std::cout << "Substring: " << sub << std::endl; // 输出 "Alice"// 从 C++11 开始,还有更多方便的功能...return 0;
}
测试用例 / 动手试试:
- 创建一个
std::string
变量firstName
并赋值为你的名字。 - 创建另一个
std::string
变量lastName
并赋值为你的姓氏。 - 将姓氏和名字连接起来,中间加一个空格,存入
fullName
变量。 - 输出
fullName
及其长度。 - 检查
fullName
中是否包含字母 ‘a’(大小写不限),如果包含则输出提示。
总结: 对于 C++ 编程,std::string
是处理文本数据的事实标准。它比 C 风格字符串更安全、更易用、功能更强大。
11. 函数:代码的模块化
随着程序变得越来越复杂,将所有代码都写在 main
函数中会变得难以管理和阅读。函数 (Function) 允许我们将代码分解成一个个独立的、可重用的模块,每个模块负责完成一个特定的任务。
为什么使用函数?
- 模块化 (Modularity): 将大问题分解成小问题,每个函数解决一个小问题,使代码结构更清晰。
- 可重用性 (Reusability): 一个函数可以被程序的不同部分多次调用,避免重复编写相同的代码。
- 可维护性 (Maintainability): 修改或修复一个功能时,通常只需要关注对应的函数,降低了出错的风险。
- 抽象 (Abstraction): 函数的使用者只需要知道函数的功能(它做什么)和接口(如何调用它),不需要关心其内部实现细节。
函数的组成:
ReturnType functionName(ParameterList) { // 函数头 (Header) / 签名 (Signature)// 函数体 (Body)// 包含执行特定任务的语句statement(s);// ...return value; // 返回值 (如果 ReturnType 不是 void)
}
ReturnType
(返回类型): 函数执行完毕后返回给调用者的数据类型。如果函数不返回任何值,则返回类型为void
。functionName
(函数名): 函数的标识符,遵循变量命名规则。ParameterList
(参数列表): 函数接收的输入数据。参数列表在括号()
内,包含零个或多个参数,每个参数由DataType ParameterName
组成,多个参数之间用逗号,
分隔。如果函数不需要输入,括号内为空或写void
(C++ 中通常留空)。{ }
(函数体): 包含函数实际执行的代码块。return value;
(返回语句): 如果函数返回类型不是void
,则必须使用return
语句返回一个类型匹配的值。return
语句也会立即结束函数的执行。void
函数可以没有return
语句,或者使用return;
来提前结束执行。
函数声明 (Declaration) / 原型 (Prototype):
在使用函数之前,需要让编译器知道这个函数的存在及其接口(返回类型、名称、参数列表)。这可以通过函数声明(也叫函数原型)来完成。函数声明通常放在调用该函数的代码之前,或者放在头文件中。
函数声明语法:
ReturnType functionName(ParameterList); // 注意末尾的分号 ;
函数声明不包含函数体。
函数定义 (Definition):
函数定义包含了函数声明的所有信息,再加上函数体,即函数的具体实现。
示例:
#include <iostream>// 函数声明 (原型) - 告诉编译器 max 函数的存在
int max(int num1, int num2); // 注意分号// 主函数 main
int main() {int a = 100;int b = 200;int result;// 调用 max 函数result = max(a, b); // 将 a 和 b 的值作为参数传递给 max 函数std::cout << "Max value is : " << result << std::endl; // 输出 200return 0;
}// 函数定义 - 实现 max 函数的功能
int max(int num1, int num2) { // 函数头与声明一致 (参数名可以不同,但类型必须一致)// 函数体int maxValue;if (num1 > num2) {maxValue = num1;} else {maxValue = num2;}return maxValue; // 返回计算出的最大值
}
执行流程:
main
函数开始执行。- 遇到
result = max(a, b);
,程序暂停main
函数的执行。 - 将
a
的值 (100) 复制给max
函数的参数num1
,将b
的值 (200) 复制给num2
(这称为按值传递 Pass by Value)。 - 跳转到
max
函数的定义处,开始执行max
函数体。 max
函数计算出maxValue
为 200。max
函数执行return maxValue;
,将 200 这个值返回给调用者 (main
函数)。- 程序回到
main
函数中调用max
的地方,将返回的 200 赋值给result
变量。 main
函数继续执行后续代码。
测试用例 / 动手试试:
- 编写一个名为
add
的函数,接收两个int
参数,返回它们的和。 - 在
main
函数中调用add
函数,传入两个数字,并将返回的结果打印出来。 - 编写一个名为
printMessage
的void
函数,接收一个std::string
参数,并在函数内部将这个字符串打印到控制台。 - 在
main
函数中调用printMessage
函数,传入一个你想要打印的消息。
函数重载 (Function Overloading):
C++ 允许定义多个同名的函数,只要它们的参数列表不同(参数个数不同 或 参数类型不同 或 两者都不同)。这称为函数重载。编译器会根据你调用函数时提供的参数来决定具体调用哪个版本的函数。
#include <iostream>int operate(int a, int b) {return a + b;
}double operate(double a, double b) {return a * b;
}int main() {std::cout << operate(5, 3) << std::endl; // 调用 int 版本,输出 8std::cout << operate(5.0, 3.0) << std::endl; // 调用 double 版本,输出 15.0return 0;
}
12. 总结与展望
恭喜你!你已经学习了 C++ 的核心基础知识,包括:
- C++ 的基本概念、编译环境和第一个程序。
- 基本数据类型、变量和常量。
- 常用的算术、关系和逻辑运算符。
- 头文件的作用和输入输出流 (
cin
,cout
) 的基本使用。 - 使用
if-else
和switch
进行条件判断。 - 使用
for
,while
,do-while
实现循环。 - 使用
break
和continue
控制循环流程。 - 数组 (
char[]
,int[]
等) 的定义和使用。 - C 风格字符串和更推荐的 C++
std::string
类。 - 函数 (声明、定义、调用、返回值、参数) 的基本概念和用法。
这些知识为你后续深入学习 C++ 打下了坚实的基础。
接下来学什么?
C++ 的世界非常广阔,基础之后还有很多重要的内容值得探索:
- 指针 (Pointers): C++ 中非常重要但也较难的概念,直接操作内存地址。
- 引用 (References): 变量的别名,常用于函数参数传递。
- 类 (Classes) 与对象 (Objects): 面向对象编程 (OOP) 的核心,封装数据和行为。
- 构造函数 (Constructors) 与析构函数 (Destructors): 对象的创建和销毁。
- 继承 (Inheritance) 与多态 (Polymorphism): OOP 的另外两大支柱,实现代码复用和灵活性。
- 模板 (Templates): 实现泛型编程,编写类型无关的代码。
- 标准模板库 (STL) 深入:
vector
,list
,map
,set
等容器,以及各种算法。 - 文件操作 (File I/O): 读取和写入文件。
- 异常处理 (Exception Handling): 更健壮地处理程序错误。
- 内存管理:
new
,delete
以及智能指针 (unique_ptr
,shared_ptr
)。
学习编程是一个持续实践的过程。多写代码、多调试、多阅读优秀代码是提高编程能力的关键。希望本教程能帮助你顺利开启 C++ 的学习之旅!
再次感谢你的阅读,如果觉得有帮助,请点赞支持!
主要参考资料: C++ 教程 | 菜鸟教程 (runoob.com)