C++零基础实践教程 函数 数组、字符串与 Vector
模块四:函数 (代码复用与模块化)
随着程序变得越来越复杂,把所有代码都堆在 main
函数里会变得难以管理和阅读。函数 (Function) 允许你将代码分解成逻辑上独立、可重用的块。这就像把一个大任务分解成几个小任务,每个小任务交给一个专门的“工人”(函数)来完成。
1. 函数的定义与调用
-
定义 (Definition): 创建一个函数,告诉编译器这个函数叫什么名字,它需要什么输入(参数),它会返回什么输出(返回值),以及它具体要做什么(函数体)。
C++// 语法: // 返回值类型 函数名(参数列表) { // // 函数体: 具体的代码逻辑 // return 返回值; // 如果返回值类型不是 void // }// 示例:一个简单的加法函数 int add(int num1, int num2) { // 返回 int, 函数名 add, 接收两个 int 参数int sum = num1 + num2;return sum; // 返回计算结果 }// 示例:一个没有返回值的问候函数 (void) void greet(std::string name) { // 返回 void (无返回值), 接收一个 string 参数std::cout << "你好, " << name << "!\n";// 没有 return 语句,或者可以写 return; 来提前结束函数 }
- 返回值类型 (Return Type): 函数执行完毕后返回的数据类型(如
int
,double
,void
表示不返回任何值)。 - 函数名 (Function Name): 给函数起一个有意义的名字(遵循变量命名规则)。
- 参数列表 (Parameter List): 函数接收的输入数据,在圆括号
()
内声明,多个参数用逗号,
分隔。每个参数都需要指定类型和名称。如果函数不需要输入,括号内为空 (void myFunc()
)。 - 函数体 (Function Body): 花括号
{}
内的代码,是函数实际执行的操作。 return
语句: 用于从函数返回值。一旦执行return
,函数立即结束。void
函数没有返回值,可以不写return
或使用return;
提前退出。
- 返回值类型 (Return Type): 函数执行完毕后返回的数据类型(如
-
调用 (Call): 在程序的其他地方(比如
C++main
函数或其他函数中)使用函数名和必要的参数来执行函数定义的代码。#include <iostream> #include <string>// (在此处或之后定义 add 和 greet 函数,或者使用下面的函数原型) int add(int num1, int num2) { /* ... */ } void greet(std::string name) { /* ... */ }int main() {int result = add(5, 3); // 调用 add 函数,传入 5 和 3 作为参数// 函数返回的值 8 被存储在 result 变量中std::cout << "5 + 3 = " << result << std::endl; // 输出 8greet("Alice"); // 调用 greet 函数,传入 "Alice"// 函数会直接输出 "你好, Alice!"int x = 10, y = 20;int sum_xy = add(x, y); // 也可以使用变量作为参数std::cout << x << " + " << y << " = " << sum_xy << std::endl; // 输出 30return 0; }
- 参数 (Arguments): 调用函数时传递给函数的实际值,其数量和类型必须与函数定义中的参数列表匹配。
2. 函数原型 (声明)
C++ 编译器是按从上到下的顺序读取代码的。如果你在调用一个函数之前还没有定义它,编译器就不知道这个函数长什么样(需要什么参数,返回什么类型),就会报错。
解决方法有两种:
-
将函数定义放在调用它的代码之前: 对于小程序可行,但项目变大后难以管理。
-
使用函数原型 (Function Prototype) 或声明 (Declaration): 在调用函数之前,先写一行函数的“签名”,告诉编译器这个函数的基本信息,但省略函数体。函数原型看起来就像函数定义的第一行,但后面跟的是分号
C++;
而不是花括号{}
。#include <iostream> #include <string>// 函数原型 (声明) int add(int num1, int num2); // 告诉编译器:有一个叫 add 的函数,接收两个 int,返回 int void greet(std::string name); // 告诉编译器:有一个叫 greet 的函数,接收 string,返回 voidint main() {// 现在可以在 main 函数中调用这些函数了,即使它们的定义在后面int result = add(15, 7);std::cout << "15 + 7 = " << result << std::endl;greet("Bob");return 0; }// 函数定义 (实现) - 可以放在 main 函数之后 int add(int num1, int num2) {return num1 + num2; }void greet(std::string name) {std::cout << "你好, " << name << "!\n"; }
最佳实践: 通常将函数原型放在源文件的开头(
#include
之后)或者单独的头文件 (.h
或.hpp
) 中,而将函数定义放在源文件 (.cpp
) 的后面或另一个.cpp
文件中。这使得代码结构更清晰。
3. 作用域 (Scope)
变量并非在程序的任何地方都可用。作用域定义了变量可以被访问的区域。
-
局部变量 (Local Variables):
- 在函数内部或某个代码块(如
if
,for
,while
的花括号{}
内)声明的变量。 - 只在声明它们的花括号
{}
内部有效。一旦程序执行离开这个代码块,局部变量就会被销毁,它们占用的内存会被释放。 - 函数的参数也属于局部变量,只在函数内部有效。
void myFunction() {int localVar = 10; // localVar 是局部变量std::cout << localVar << std::endl; // 在函数内可以访问 }int main() {int mainVar = 5; // mainVar 是 main 函数的局部变量// std::cout << localVar << std::endl; // 错误!无法访问 myFunction 的局部变量 localVarmyFunction();return 0; }
- 在函数内部或某个代码块(如
-
全局变量 (Global Variables):
- 在所有函数之外声明的变量。
- 从声明点开始,到整个文件结束都有效,可以被该文件中它之后的所有函数访问。
#include <iostream>int globalVar = 100; // 全局变量void printGlobal() {std::cout << "printGlobal: " << globalVar << std::endl; // 可以访问全局变量globalVar = 200; // 也可以修改全局变量 }int main() {std::cout << "main (before): " << globalVar << std::endl; // 输出 100printGlobal(); // 调用函数,修改了 globalVarstd::cout << "main (after): " << globalVar << std::endl; // 输出 200return 0; }
警告: 应尽量避免使用全局变量!
- 难以追踪: 程序的任何地方都可能修改全局变量的值,使得调试和理解代码流程变得困难。
- 命名冲突: 如果不同文件或库定义了同名的全局变量,可能导致链接错误或意想不到的行为。
- 降低模块性: 函数依赖于全局变量,使得函数不够独立,难以复用。
优先使用局部变量和函数参数/返回值来传递数据。
4. 传值调用 (Pass by Value) vs 传引用调用 (Pass by Reference, &
)
当我们将变量作为参数传递给函数时,有两种主要方式:
-
传值调用 (Pass by Value): 这是 C++ 的默认方式。
- 函数接收到的是实参(调用时传入的值)的一个副本 (copy)。
- 在函数内部对这个副本参数的任何修改,不会影响到函数外部的原始实参变量。
- 优点: 安全,不会意外修改原始数据。
- 缺点: 如果传递的数据很大(比如大型结构体或对象),创建副本会有效率开销。
#include <iostream>void modifyValue(int val) { // val 是 num 的一个副本val = val * 2;std::cout << "Inside function, val = " << val << std::endl; // 输出 20 }int main() {int num = 10;std::cout << "Before function call, num = " << num << std::endl; // 输出 10modifyValue(num);std::cout << "After function call, num = " << num << std::endl; // 仍然输出 10,原始 num 未改变return 0; }
-
传引用调用 (Pass by Reference): 通过在函数参数类型后加上
&
符号实现。- 函数接收到的是原始实参变量的一个引用 (reference),可以理解为原始变量的别名。函数内部操作这个引用参数,实际上就是在操作原始变量。
- 在函数内部对引用参数的修改,会影响到函数外部的原始实参变量。
- 优点:
- 可以直接修改原始实参的值(例如
swap
函数)。 - 避免了创建副本的开销,对于传递大型数据结构更高效。
- 可以直接修改原始实参的值(例如
- 缺点: 可能会无意中修改原始数据,需要小心使用。
#include <iostream>void modifyReference(int& ref) { // ref 是 num 的一个引用(别名)ref = ref * 2;std::cout << "Inside function, ref = " << ref << std::endl; // 输出 20 }// 经典的 swap 函数示例 void swap(int& a, int& b) { // 接收两个 int 引用int temp = a;a = b;b = temp; }int main() {int num = 10;std::cout << "Before function call, num = " << num << std::endl; // 输出 10modifyReference(num);std::cout << "After function call, num = " << num << std::endl; // 输出 20,原始 num 被改变了!int x = 5, y = 9;std::cout << "Before swap: x = " << x << ", y = " << y << std::endl; // 输出 5, 9swap(x, y);std::cout << "After swap: x = " << x << ", y = " << y << std::endl; // 输出 9, 5return 0; }
-
常量引用 (
C++const &
): 如果你希望通过引用传递来避免复制开销,但又不希望函数修改原始数据,可以使用常量引用。void printLargeData(const std::string& data) { // 使用常量引用传递字符串std::cout << "Data: " << data << std::endl;// data = "changed"; // 错误!不能通过常量引用修改数据 }
这是 C++ 中传递大型对象(如
string
,vector
)给函数时常用的高效且安全的方式。
5. 函数重载 (Function Overloading)
C++ 允许你定义多个同名的函数,只要它们的参数列表不同即可。参数列表的不同可以体现在:
- 参数的数量不同。
- 参数的类型不同。
- 参数的顺序不同(如果类型也不同)。
注意: 函数的返回值类型不能作为区分重载函数的依据。
编译器会根据你调用函数时提供的参数类型和数量来自动选择匹配哪个版本的重载函数。
C++
#include <iostream>
#include <string>// 重载 print 函数
void print(int value) {std::cout << "Integer: " << value << std::endl;
}void print(double value) {std::cout << "Double: " << value << std::endl;
}void print(std::string value) {std::cout << "String: \"" << value << "\"" << std::endl;
}int main() {print(10); // 调用 print(int)print(3.14); // 调用 print(double)print("Hello"); // 调用 print(std::string) - C风格字符串字面量可以隐式转换为 std::stringreturn 0;
}
函数重载使得你可以为逻辑上相似但处理不同数据类型的操作使用同一个函数名,让代码更直观。
6. 实战练习
-
重构简易计算器:
- 打开你之前编写的
calculator.cpp
(模块二的项目)。 - 定义四个函数:
double add(double n1, double n2)
,double subtract(double n1, double n2)
,double multiply(double n1, double n2)
,double divide(double n1, double n2)
。 - 将原来
main
函数中的加、减、乘、除计算逻辑分别移动到这四个函数中,并使用return
返回结果。 - 在
divide
函数内部处理除数为零的情况(例如,如果n2
为 0,可以打印错误信息并返回一个特殊值,如0.0
或NaN
- 需要<cmath>
)。 - 修改
main
函数,让它调用这些新定义的函数来获取计算结果并输出。记得在main
函数之前添加函数原型或将函数定义放在main
之前。
- 打开你之前编写的
-
为猜数字游戏添加输入验证函数:
- 目标:创建一个函数,比如
int getValidIntInput(const std::string& prompt)
,它负责向用户显示提示信息 (prompt
),读取用户的整数输入,并确保用户输入的是一个有效的整数。如果输入无效,应提示用户重新输入,直到输入有效为止。 - 思路:
- 函数内部使用一个循环 (
while(true)
或do-while
)。 - 在循环里,打印
prompt
,然后使用std::cin >> variable;
尝试读取。 - 检查
std::cin
的状态:if (std::cin.fail())
: 如果读取失败(例如用户输入了字母),说明输入无效。- 打印错误提示。
std::cin.clear();
// 清除cin
的错误状态标志。std::cin.ignore(10000, '\n');
// 忽略掉输入缓冲区中错误的内容,直到遇到换行符或忽略了足够多的字符。- 使用
continue;
跳过本次循环的剩余部分,重新提示输入。
else
: 如果读取成功,说明输入有效,使用break;
跳出循环。
- 函数最后返回读取到的有效整数。
- 函数内部使用一个循环 (
- 应用: 修改你的
guessing_game.cpp
,用这个getValidIntInput
函数来代替原来的std::cin >> guess;
,让游戏更健壮。
- 目标:创建一个函数,比如
7. 实战项目 3: 增强版计算器
现在,我们将函数知识和之前的控制流知识结合起来,做一个功能更强、交互性更好的计算器。
- 目标:
- 使用函数实现各个计算操作(加、减、乘、除,可以额外添加取模
%
、求幂pow
等)。 - 程序启动后显示一个菜单,列出可用的操作选项(例如:1. 加法, 2. 减法, ..., 0. 退出)。
- 用户通过输入数字选择操作。
- 程序根据用户的选择,提示用户输入操作数,然后调用相应的函数进行计算并显示结果。
- 完成一次计算后,再次显示菜单,让用户可以继续进行其他计算,直到用户选择退出为止。
- 使用函数实现各个计算操作(加、减、乘、除,可以额外添加取模
- 涉及知识点: 函数定义与调用 (核心),
switch
或if-else if-else
(处理菜单选择),循环 (while
或do-while
控制程序主流程,直到用户退出),基本输入输出。 - 建议步骤:
- 定义计算函数: 像练习 1 那样,为每个运算(加、减、乘、除、取模、求幂等)编写独立的函数。求幂可能需要
#include <cmath>
并使用pow(base, exponent)
函数。注意处理除零、取模的除数为零等情况。 - 定义显示菜单函数: 可以写一个
void displayMenu()
函数,专门负责打印操作选项。 - 主函数
main
逻辑:- 使用一个
do-while
或while
循环来保持程序运行,直到用户选择退出。 - 在循环内部:
- 调用
displayMenu()
显示菜单。 - 提示用户输入选项,并读取用户的选择 (
int choice;
)。可以使用上面练习中写的getValidIntInput
函数来确保输入是有效的整数。 - 使用
switch (choice)
或if-else if-else
结构判断用户的选择:case 1:
(加法)- 提示用户输入两个操作数 (
num1
,num2
)。 - 调用
add(num1, num2)
函数。 - 输出结果。
break;
- 提示用户输入两个操作数 (
case 2:
(减法) ... 以此类推。case 0:
(退出)- 输出告别信息。
- 设置循环退出条件(例如,如果用
while(run)
,则设置run = false;
)。 break;
default:
(无效选项)- 输出提示信息。
break;
- 调用
- 循环结束后,
return 0;
。
- 使用一个
- 定义计算函数: 像练习 1 那样,为每个运算(加、减、乘、除、取模、求幂等)编写独立的函数。求幂可能需要
这个项目能很好地锻炼你组织代码、使用函数进行模块化设计的能力。
模块五:数组、字符串与 Vector (处理数据集合)
目前我们处理的都是单个数据。但很多时候,我们需要处理一组数据,比如一个班级所有学生的分数,或者一个人的姓名(由多个字符组成)。这一模块将介绍如何处理这些数据集合。
1. 数组 (Array)
数组是最基本的数据集合,它可以在内存中连续存储固定数量的相同类型的元素。
-
声明:
C++数据类型 数组名[数组大小];
数组大小必须是一个常量表达式(在编译时就能确定)。int scores[5]; // 声明一个可以存储 5 个 int 类型分数的数组 double prices[10]; // 声明一个可以存储 10 个 double 类型价格的数组 char grades[3]; // 声明一个可以存储 3 个 char 类型等级的数组
-
初始化: 可以在声明时使用花括号
C++{}
提供初始值。int scores[5] = {85, 92, 78, 95, 88}; // 提供所有 5 个元素的初始值 double prices[10] = {9.9, 15.5, 8.0}; // 只提供前 3 个,其余元素会被自动初始化为 0 char grades[] = {'A', 'B', 'C'}; // 可以不指定大小,编译器会根据初始值数量自动推断 (大小为 3) int counts[5] = {}; // 所有元素初始化为 0
-
访问元素: 通过索引 (index) 来访问数组中的特定元素。索引从 0 开始!对于大小为
C++N
的数组,有效的索引范围是0
到N-1
。使用方括号[]
进行访问。int scores[5] = {85, 92, 78, 95, 88}; std::cout << "第一个分数: " << scores[0] << std::endl; // 输出 85 (索引 0) std::cout << "第三个分数: " << scores[2] << std::endl; // 输出 78 (索引 2)scores[0] = 90; // 修改第一个元素的值 std::cout << "修改后的第一个分数: " << scores[0] << std::endl; // 输出 90// std::cout << scores[5] << std::endl; // 错误!索引越界 (有效的索引是 0 到 4)// 访问越界是危险的,可能导致程序崩溃或不可预测的行为!
警告: C++ 不会自动检查数组索引是否越界。访问无效索引是常见的、危险的错误来源。
-
遍历数组: 通常使用
C++for
循环来遍历数组的所有元素。const int NUM_SCORES = 5; // 使用常量表示数组大小是好习惯 int scores[NUM_SCORES] = {85, 92, 78, 95, 88}; double total = 0;for (int i = 0; i < NUM_SCORES; ++i) { // i 从 0 循环到 NUM_SCORES - 1std::cout << "分数 " << (i + 1) << ": " << scores[i] << std::endl;total += scores[i]; // 累加分数 } double average = total / NUM_SCORES; std::cout << "平均分: " << average << std::endl;
-
数组作为函数参数:
- 当你将数组传递给函数时,实际上传递的是数组第一个元素的内存地址(一个指针)。函数不会创建整个数组的副本。
- 因此,在函数内部对数组参数的修改会影响原始数组。
- 因为传递的只是地址,函数本身不知道数组的大小。通常需要将数组大小作为另一个参数传递给函数。
#include <iostream>// 函数原型,接收一个 int 数组和它的大小 void printArray(int arr[], int size); void modifyArray(int arr[], int size);int main() {const int ARRAY_SIZE = 3;int numbers[ARRAY_SIZE] = {10, 20, 30};std::cout << "原始数组: ";printArray(numbers, ARRAY_SIZE); // 输出 10 20 30 modifyArray(numbers, ARRAY_SIZE);std::cout << "修改后数组: ";printArray(numbers, ARRAY_SIZE); // 输出 11 21 31 (原始数组被修改了)return 0; }// 打印数组元素的函数 void printArray(int arr[], int size) { // arr 实际上是一个指向数组首元素的指针for (int i = 0; i < size; ++i) {std::cout << arr[i] << " ";}std::cout << std::endl; }// 修改数组元素的函数 void modifyArray(int arr[], int size) {for (int i = 0; i < size; ++i) {arr[i] = arr[i] + 1; // 直接修改了原始数组的元素} }
2. C 风格字符串 (简要介绍)
在 C++ 引入 std::string
之前,主要使用字符数组来表示字符串,并以一个特殊的空终止符 \0
(null terminator) 结尾来标记字符串的结束。
C++
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 需要手动添加 \0
char name[] = "Alice"; // 字符串字面量会自动在末尾添加 \0 (实际大小是 6)
你需要使用 <cstring>
(或 <string.h>
) 中的函数(如 strlen
计算长度 - 不含 \0
, strcpy
复制字符串, strcat
拼接字符串)来操作它们。
缺点:
- 大小固定: 数组大小在编译时确定,难以处理长度可变的文本。
- 容易出错: 必须手动管理
\0
,并且像strcpy
这样的函数如果不小心,很容易造成缓冲区溢出(写入的数据超出了数组分配的空间),这是一个严重的安全隐患。 - 操作不便: 拼接、比较等操作不如
std::string
直观。
结论: 了解 C 风格字符串有助于理解一些底层概念和旧代码,但在现代 C++ 编程中,强烈建议使用 std::string
。
3. std::string
(现代 C++ 字符串 - 重点)
std::string
是 C++ 标准库提供的一个类,专门用于方便、安全地处理字符串(文本)。
-
使用: 需要包含头文件
#include <string>
。 -
创建与初始化:
C++#include <string> #include <iostream>int main() {std::string s1; // 创建一个空字符串std::string s2 = "Hello"; // 从 C 风格字符串字面量初始化std::string s3 = s2; // 复制 s2 来创建 s3std::string s4("World"); // 另一种初始化方式std::string s5(5, 'c'); // 创建包含 5 个 'c' 的字符串 ("ccccc")std::cout << "s2: " << s2 << std::endl;std::cout << "s3: " << s3 << std::endl;std::cout << "s4: " << s4 << std::endl;std::cout << "s5: " << s5 << std::endl;return 0; }
-
赋值: 使用
C++=
运算符。std::string message = "Initial message"; message = "New content"; // 赋值
-
拼接 (Concatenation): 使用
C+++
或+=
运算符。std::string firstName = "John"; std::string lastName = "Doe"; std::string fullName = firstName + " " + lastName; // 使用 + std::cout << "Full Name: " << fullName << std::endl; // 输出 "John Doe"std::string greeting = "Hi, "; greeting += firstName; // 使用 += greeting += "!"; std::cout << greeting << std::endl; // 输出 "Hi, John!"
-
获取长度: 使用
C++.length()
或.size()
成员函数 (两者功能相同)。std::string text = "C++ is fun!"; std::cout << "Length: " << text.length() << std::endl; // 输出 11
-
输入:
std::cin >> myString;
: 从键盘读取字符串,遇到空白字符(空格、制表符、换行符)时停止读取。getline(std::cin, myString);
: 读取一整行输入,直到遇到换行符\n
为止(换行符本身会被读取并丢弃)。当你需要读取包含空格的名字或句子时,应该使用getline
。
#include <string> #include <iostream>int main() {std::string word;std::cout << "Enter a word: ";std::cin >> word; // 如果输入 "Hello World", word 只会得到 "Hello"std::cout << "You entered the word: " << word << std::endl;std::string line;std::cout << "Enter a full line: ";// *** 重要: 如果之前使用了 cin >> 读取,需要先忽略掉上次输入留下的换行符 ***// std::cin.ignore(10000, '\n'); // 或者更简单的 std::wsgetline(std::cin >> std::ws, line); // std::ws 会跳过输入流开头的所有空白字符// 如果输入 "Hello World", line 会得到 "Hello World"std::cout << "You entered the line: \"" << line << "\"" << std::endl;return 0; }
注意
std::ws
或cin.ignore()
! 在cin >>
和getline
混合使用时,cin >>
读取后会把换行符留在输入缓冲区,getline
看到这个换行符会立刻停止读取,导致似乎“跳过”了输入。使用getline(std::cin >> std::ws, line)
或在getline
前加std::cin.ignore(...)
可以解决这个问题。 -
其他常用操作:
std::string
还提供了很多有用的功能,如比较 (==
,!=
,<
,>
), 访问单个字符 ([]
或.at()
), 查找子串 (.find()
), 提取子串 (.substr()
) 等。你可以在后续学习中探索。
std::string
的优势:
- 自动内存管理: 你不需要关心内存分配和
\0
。 - 安全: 不容易发生缓冲区溢出。
- 方便: 提供了丰富的成员函数来操作字符串。
4. std::vector
(现代 C++ 动态数组 - 入门)
原始数组最大的限制是大小固定。如果你在写程序时不知道需要存储多少个元素(比如,用户要输入多少个分数),数组就不够灵活了。std::vector
就是解决这个问题的利器,它是一个大小可变的动态数组。
-
使用: 需要包含头文件
#include <vector>
。 -
创建:
C++std::vector<数据类型> 变量名;
#include <vector> #include <string> #include <iostream>int main() {std::vector<int> scores; // 创建一个空的 int 类型 vectorstd::vector<double> prices = {9.9, 15.5, 8.0}; // 初始化包含 3 个 doublestd::vector<std::string> names; // 创建一个空的 string 类型 vectorstd::vector<char> letters(5, 'a'); // 创建包含 5 个 'a' 的 vectorstd::cout << "Initial size of scores: " << scores.size() << std::endl; // 输出 0std::cout << "Initial size of prices: " << prices.size() << std::endl; // 输出 3return 0; }
-
添加元素: 使用
C++.push_back(元素值)
在 vector 的末尾添加一个元素。Vector 会自动管理内存,在需要时扩展容量。std::vector<int> numbers; numbers.push_back(10); // numbers 现在是 {10} numbers.push_back(20); // numbers 现在是 {10, 20} numbers.push_back(30); // numbers 现在是 {10, 20, 30} std::cout << "Size after push_back: " << numbers.size() << std::endl; // 输出 3
-
访问元素:
变量名[索引]
: 类似数组,使用方括号和从 0 开始的索引。不进行边界检查,如果索引越界,行为未定义(危险!)。变量名.at(索引)
: 也使用索引访问,但会进行边界检查。如果索引无效,它会抛出一个异常(使程序更安全,虽然异常处理我们还没学)。推荐使用.at()
进行访问,尤其是在不确定索引是否有效时。
std::vector<int> data = {5, 10, 15}; std::cout << "Element at index 0: " << data[0] << std::endl; // 输出 5 std::cout << "Element at index 1: " << data.at(1) << std::endl; // 输出 10data[0] = 7; // 修改元素 std::cout << "Modified element at index 0: " << data.at(0) << std::endl; // 输出 7// std::cout << data[3] << std::endl; // 危险!索引越界 // std::cout << data.at(3) << std::endl; // 安全!会抛出异常,而不是访问无效内存
-
获取大小: 使用
C++.size()
成员函数,返回 vector 中当前元素的数量。std::vector<double> values = {1.1, 2.2}; std::cout << "Number of values: " << values.size() << std::endl; // 输出 2 values.push_back(3.3); std::cout << "Number of values now: " << values.size() << std::endl; // 输出 3
-
遍历 Vector:
- 使用传统
for
循环和索引: C++std::vector<std::string> fruits = {"Apple", "Banana", "Cherry"}; for (int i = 0; i < fruits.size(); ++i) { // 注意循环条件是 < fruits.size()std::cout << "Fruit " << i << ": " << fruits.at(i) << std::endl; // 使用 .at() 更安全 }
- 使用范围
for
循环 (Range-based for loop - C++11 及以后,推荐): 语法更简洁,不易出错。 C++
范围std::vector<std::string> fruits = {"Apple", "Banana", "Cherry"}; std::cout << "Fruits using range-based for loop:\n"; // for (元素类型 元素变量名 : 容器名) for (std::string fruit : fruits) { // 对 fruits 中的每个元素,将其复制到 fruit 变量std::cout << "- " << fruit << std::endl; }// 如果需要在循环中修改元素,或避免复制大型元素,使用引用 (&) std::vector<int> nums = {1, 2, 3}; for (int& num : nums) { // num 是 vector 中元素的引用num = num * 2; // 直接修改 vector 中的元素 } // 现在 nums 是 {2, 4, 6}// 如果只想读取元素且避免复制,使用常量引用 (const &) for (const std::string& fruit : fruits) { // fruit 是元素的常量引用,高效且安全std::cout << "- " << fruit << std::endl;// fruit = "Orange"; // 错误!不能通过常量引用修改 }// 使用 auto 自动推断类型,更简洁 for (auto& num : nums) { // 自动推断 num 为 int&num += 1; } for (const auto& fruit : fruits) { // 自动推断 fruit 为 const std::string&std::cout << "- " << fruit << std::endl; }
for
循环是遍历vector
(以及许多其他容器) 的首选方式。
- 使用传统
std::vector
的优势:
- 动态大小: 可以根据需要增长,非常灵活。
- 自动内存管理: 无需手动
new
和delete
。 - 方便: 提供
.push_back()
,.size()
,.at()
等实用函数。 - 安全:
.at()
提供边界检查。 - 高效: 通常实现得很高效。
结论: 在现代 C++ 中,当你需要一个可变大小的数组时,std::vector
通常是比原始数组更好的选择。
5. 实战项目 4: 简单的学生成绩管理
这个项目将综合运用 std::vector
, std::string
以及函数知识。
- 目标: 编写一个程序,允许用户输入若干学生的名字和对应的分数,然后计算并显示所有学生的平均分、最高分和最低分(以及获得最高/最低分的学生名字)。
- 涉及知识点:
std::vector<std::string>
,std::vector<double>
, 循环 (for
), 输入输出 (std::cin
,std::cout
,getline
,std::ws
), 函数(用于计算和查找)。 - 建议步骤:
- 包含头文件:
#include <iostream>
,#include <vector>
,#include <string>
,#include <limits>
(可能需要用到数字极限值)。 - 声明 Vectors: 在
main
函数中声明两个 vector:std::vector<std::string> studentNames;
和std::vector<double> studentScores;
。 - 获取学生数量: 提示用户要输入多少个学生的信息,读取数量
int numStudents;
。可以加入输入验证,确保输入的是正整数。 - 循环读取信息: 使用
for
循环,执行numStudents
次:- 提示用户输入第
i+1
个学生的名字。使用getline(std::cin >> std::ws, name)
来读取可能包含空格的名字。 - 将读取到的名字
name
添加到studentNames
vector 中 (studentNames.push_back(name);
)。 - 提示用户输入该学生的分数。读取分数
double score;
。可以加入输入验证,确保分数是有效的数字(比如在 0-100 之间)。 - 将读取到的分数
score
添加到studentScores
vector 中 (studentScores.push_back(score);
)。
- 提示用户输入第
- 编写计算/查找函数 (并将它们放在
main
之前或之后,并在main
之前提供原型):double calculateAverage(const std::vector<double>& scores)
:- 接收一个
scores
vector 的常量引用。 - 检查 vector 是否为空,如果为空返回 0 或其他合适的值。
- 使用循环累加所有分数。
- 返回 总分数 /
scores.size()
。
- 接收一个
double findHighestScore(const std::vector<double>& scores)
:- 接收
scores
vector 的常量引用。 - 处理空 vector 的情况。
- 初始化一个变量
highest
为第一个分数scores[0]
(或可能的最低分std::numeric_limits<double>::lowest()
)。 - 使用循环遍历 vector,如果当前分数
score
大于highest
,则更新highest = score
。 - 返回
highest
。
- 接收
double findLowestScore(const std::vector<double>& scores)
:- 类似
findHighestScore
,但比较score < lowest
。初始化lowest
为scores[0]
(或可能的最高分std::numeric_limits<double>::max()
)。
- 类似
- (可选)
int findStudentIndex(const std::vector<double>& scores, double targetScore)
:- 接收
scores
vector 和一个目标分数targetScore
。 - 遍历
scores
vector,找到第一个等于targetScore
的元素的索引并返回。如果找不到,返回 -1。
- 接收
- 在
main
函数中调用函数并输出结果:- 检查
studentScores
是否为空。如果不为空:- 调用
calculateAverage(studentScores)
并输出平均分。 - 调用
findHighestScore(studentScores)
得到最高分highScore
。 - 调用
findLowestScore(studentScores)
得到最低分lowScore
。 - (如果实现了
findStudentIndex
) 调用findStudentIndex(studentScores, highScore)
得到最高分学生的索引highIndex
。如果highIndex != -1
,则输出最高分和对应的学生名字studentNames[highIndex]
。 - 类似地找到并输出最低分和对应的学生名字。
- 调用
- 如果 vector 为空,输出提示信息。
- 检查
- 包含头文件:
这个项目会让你熟练掌握 vector
和 string
的基本操作,并进一步体会函数在组织代码中的作用。
相关文章:
C++零基础实践教程 函数 数组、字符串与 Vector
模块四:函数 (代码复用与模块化) 随着程序变得越来越复杂,把所有代码都堆在 main 函数里会变得难以管理和阅读。函数 (Function) 允许你将代码分解成逻辑上独立、可重用的块。这就像把一个大任务分解成几个小任务,每个小任务交给一个专门的“…...
照片处理工具:基于HTML与JavaScript实现详解
在当今数字时代,处理照片已成为日常需求。 本文将详细介绍一个基于HTML和JavaScript的照片处理工具的实现原理,这个工具可以调整图片尺寸、格式,并精确控制输出文件大小。 实现如下,不需要任何编辑器,txt文本、浏览器就行!! 工具功能概述 这个照片处理工具提供以下核心…...
MyBatis-OGNL表达式
介绍 OGNL(Object-Graph Navigation Language)是一种强大的表达式语言,用于获取和设置Java对象图中的属性。在MyBatis中,OGNL常用于动态SQL构建,如条件判断、循环等。以下是关于OGNL表达式的整合信息,包括…...
Web Worker在uniapp鸿蒙APP中的深度应用
文章目录 一、Web Worker核心概念解析1.1 什么是Web Worker?1.2 为什么在鸿蒙APP中使用Web Worker?1.3 性能对比实测 二、uniapp中的Web Worker完整实现2.1 基础配置步骤2.1.1 项目结构准备2.1.2 鸿蒙平台特殊配置 2.2 Worker脚本编写规范2.3 主线程通信…...
无人机故障冗余设计技术要点与难点!
一、技术要点 1. 冗余架构设计 硬件冗余:关键部件(飞控、电机、电池、通信模块)采用双余度或三余度设计,例如: 双飞控系统:主飞控失效时,备用飞控无缝接管。 电机动力冗余:六轴无…...
MySQL数据库表查询
测试表company.employee5 mysql> create database company; #创建一个库; 创建一个测试表: mysql> CREATE TABLE company.employee5(id int primary key auto_increment not null,name varchar(30) not null,sex enum(male,female) default male not null,hi…...
ADB的安装及抓取日志(2)
三、ADB抓取日志 在使用ADB抓取日志前,首先要保证电脑已经安装并配置ADB,在上一节已经验证完成。连接设备:可通过USB或者WI-FI,将安卓设备与电脑连接,并启用USB调试模式,此处我选择的是通过电脑与安卓设备…...
【C++】 —— 笔试刷题day_17
一、小乐乐改数字 题目解析 这道题,它们给定一个数,我们要对它进行修改;如果某一位是奇数,就把它变成1,;如果是偶数,就把它变成0; 让我们输出最后得到的数。 算法思路 这道题,总体…...
traceId传递
1、应用内传递通过ThreadLocal,InheritableThreadLocal传递 2、跨进程的应用间传递,这种会涉及到远程rpc通信,mq通信,数据库通信等。 feign:拦截器中改变请求头 feign.RequestInterceptor, 这个机制能够实现修改请求对象的目的,…...
自然科技部分详解
光的反射 凸面镜与凹面镜 凸透镜和凹透镜 空气开关原理 短路是指电路中突然的电流过大,这会让线圈的磁性增大,来克服内设的弹簧导致断开 过载会让电流增大,两金属片受热膨胀触发断开 核电荷数是指原子核所带的电荷数。 在原子中…...
蓝桥杯 9. 九宫幻方
九宫幻方 原题目链接 题目描述 小明最近在教邻居家的小朋友小学奥数,而最近正好讲述到了三阶幻方这个部分。 三阶幻方是指将 1 ~ 9 不重复地填入一个 33 的矩阵中,使得每一行、每一列和每一条对角线的和都是相同的。 三阶幻方又被称作九宫格&#x…...
算法——希尔排序
目录 一、希尔排序定义 二、希尔排序原理 三、希尔排序特点 四、两种解法 五、代码实现 一、希尔排序定义 希尔排序是一种基于插入排序的排序算法,也被称为缩小增量排序。它通过将待排序的数组分割成若干个子序列,对子序列进行排序,然后…...
亚马逊热销变维权?5步搭建跨境产品的安全防火墙
“产品热卖,引来维权”——这已经悄然成为越来越多跨境卖家的“热销烦恼”。曾经拼品拼量,如今却要步步谨慎。商标侵权、专利投诉、图片盗用……这些问题一旦发生,轻则下架、账号被限,重则冻结资金甚至封店。 别让“热销”变“受…...
20250416-Python 中常见的填充 `pad` 方法
Python 中常见的填充 pad 方法 在 Python 中,pad 方法通常与字符串或数组操作相关,用于在数据的前后填充特定的值,以达到指定的长度或格式。以下是几种常见的与 pad 相关的用法: 1. 字符串的 pad 操作 虽然 Python 的字符串没有…...
JavaEE-0416
今天修复了一个查询数据时数据显示哈希码: 搜索检阅后得到显示该格式的原因: 重写 POJO 类的 toString 方法 在 Java 编程中,默认情况下,对象的 toString() 方法会返回类似于 com.cz.pojo.Score2a266d09 的字符串。这是由于默认…...
团体程序设计天梯赛L2-008 最长对称子串
对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定Is PAT&TAP symmetric?,最长对称子串为s PAT&TAP s,于是你应该输出11。 输入格式: 输入在一行中给出长度不超过1000的非空字符串。 输出格式&…...
命令模式 (Command Pattern)
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。该模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。 一、基础 1.1 意图 将请求封…...
Elasticsearch 8.18 中提供了原生连接 (Native Joins)
作者:来自 Elastic Costin Leau 探索 LOOKUP JOIN,这是一条在 Elasticsearch 8.18 的技术预览中提供的新 ES|QL 命令。 很高兴宣布 LOOKUP JOIN —— 这是一条在 Elasticsearch 8.18 的技术预览中提供的新 ES|QL 命令,旨在执行左 joins 以进行…...
在线终端(一个基于 Spring Boot 的在线终端模拟器,实现了类 Linux 命令行操作功能)
Online Terminal 一个基于 Spring Boot 的在线终端模拟器,实现了类 Linux 命令行操作功能。 功能特点 模拟 Linux 文件系统操作支持基础的文件和目录管理命令提供文件内容查看和编辑功能支持文件压缩和解压缩操作 快速开始 环境要求 JDK 8Maven 3.6 运行项目 克隆项目到…...
vue+electron ipc+sql相关开发(三)
在 Electron 中使用 IPC(Inter-Process Communication)与 SQLite 数据库进行通信是一个常见的模式,特别是在需要将数据库操作从渲染进程(Vue.js)移到主进程(Electron)的情况下。这样可以更好地管理数据库连接和提高安全性。下一篇介绍结合axios写成通用接口形式,虽然没…...
C++静态变量多线程中的未定义行为
静态变量,是 C 程序员最早接触的语言特性之一。它有状态、生命周期长、初始化一次,用起来真是香。 但只要程序一旦进入多线程的世界,很多你原以为“稳定可靠”的写法,可能就突然开始“不对劲”了。静态变量首当其冲。 今天我们就…...
黑马商城项目(二) Docker
一、Docker快速入门 安装Docker - 飞书云文档 二、命令解读 常见命令: 数据卷: 案例1 数据卷挂载: 案例2 本地目录挂载: 挂载到指定目录能够保存数据(即使Mysql容器被删除) docker run -d \--name mysql …...
玩转Docker | 使用Docker部署Memos笔记工具
玩转Docker | 使用Docker部署Memos笔记工具 前言一、Memos介绍Memos简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署Memos服务下载镜像创建容器创建容器检查容器状态检查服务端口安全设置四、访问Memos服务访问Memos首页注册账号五、基本使用…...
c#从ftp服务器下载文件读取csv
从 FTP 服务器下载文件的功能,并且支持根据文件名称的前缀或直接文件名进行查找和下载。以下是对代码的一些建议和修改,以确保它能够满足您的需求,尤其是如果您希望仅下载特定类型的文件(例如 .csv 文件) using Syste…...
电脑知识 | TCP通俗易懂详解 <三>tcp首部中ACK、SYN、FIN等信息填写案例_握手时
目录 一、👋🏻前言 二、🤝🏻握手时的快递单 1.👫第一次握手(发送方) 2.👫第二次握手(收件方) 3.👫第三次握手(发件方)…...
go学习记录(第二天)
Java里面的类对象可以对应go里面的结构体吗 表格对比 Java 类 (Class)Go 结构体 (Struct)封装数据和行为(字段方法)主要封装数据(字段),方法通过接收者关联支持继承(extends…...
Docker 中启动 Nginx 容器
文章目录 1. 快速运行 Nginx 容器从 Docker Hub 拉取官方镜像并运行:验证访问: 2. 挂载自定义配置和静态文件步骤: 3. 常用操作命令4. 生产环境建议使用 Docker Compose关键优化: 5. 调试技巧6. 常见问题解决 1. 快速运行 Nginx 容…...
windows 11 安装 redis
在 Windows 11 上安装 Redis 可以采用几种不同的方法,这里介绍几种常见的方法: 方法 1:使用 Microsoft Store Windows 11 提供了 Microsoft Store,你可以直接从那里安装 Redis。 打开 Microsoft Store。 在搜索框中输入 “Redi…...
5. k8s 之 pod原理与使用
Kubernetes Pod 原理详解 1. Pod 的部署方式 Pod 是 Kubernetes 的最小调度单元,其部署方式分为 声明式(YAML) 和 命令式(kubectl) 两种: (1) 声明式部署(推荐) 通过 YAML 文件定…...
人形机器人动作策略 ∼ 人类动作策略
25年3月来自UCSD、CMU、西雅图 UW、MIT 和 Apple 公司的论文“Humanoid Policy ∼ Human Policy”。 利用多样化数据训练人形机器人的操作策略,可以增强其在跨任务和平台的鲁棒性和泛化能力。然而,仅从机器人演示中学习需要耗费大量的人力,需…...
MySQL事务详解:从5.7到8.0的变化
MySQL事务详解:从5.7到8.0的变化 引言 在关系型数据库管理系统(RDBMS)中,事务是一个核心概念,它确保了数据的一致性和可靠性。MySQL作为最流行的开源RDBMS之一,其事务处理机制在不同的版本中经历了重要的…...
conda常用命令简解
以下是conda常用命令的汇总: 创建一个新环境: conda create -n your_env_name pythonX.X 激活某个环境: activate your_env_name 安装包: conda install [package] 查看安装了哪些包: conda list 查看当前有哪些虚拟环境&…...
数据科学与机器学习:前沿技术研究
数据科学与机器学习:前沿技术研究 摘要 本文探讨了数据科学与机器学习领域的三个前沿方向:自适应机器学习模型、联邦学习隐私与保护以及多模态数据处理。通过理论分析、算法设计和实验验证,展示了这些技术在解决实际问题中的潜力和挑战。自适应机器学习模型能够根据数据变化…...
个人博客测试报告
作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉…...
Sentinel源码—3.ProcessorSlot的执行过程一
大纲 1.NodeSelectorSlot构建资源调用树 2.LogSlot和StatisticSlot采集资源的数据 3.Sentinel监听器模式的规则对象与规则管理 4.AuthoritySlot控制黑白名单权限 5.SystemSlot根据系统保护规则进行流控 1.NodeSelectorSlot构建资源调用树 (1)Entry的处理链的执行入口 (2…...
datagrip连接mysql问题5.7.26
1.Case sensitivity: plainmixed, delimitedexac Remote host terminated the handshake. 区分大小写:plain混合,分隔exac 远程主机终止了握手。 原因:usessl 参数用于指定是否使用 SSL(Secure Sockets Layer)加密来保护数据传…...
【电路笔记】-变压器构造
变压器构造 文章目录 变压器构造1、概述2、变压器铁芯的构造3、变压器叠片4、变压器绕组排列5、变压器点定位6、变压器铁芯损耗6.1 磁滞损耗6.2 涡流损耗6.3 铜损耗一个简单的双绕组变压器构造包括每个绕组分别缠绕在一个独立的软铁肢或磁芯上,这提供了必要的磁路。 1、概述 …...
阿里云集群开启debug
1、安装 kubectl Macos brew install kubectl Windows: https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-windows/ 下载后,放到任意目录 2、配置连接信息 mac 将以下内容复制到计算机 $HOME/.kube/config 文件下: windows 不同集…...
继承-C++
继承在我们日常中经常指我们的人伦关系中的父子关系,孩子继承父母的基因、习惯之类的,孩子也会有自己的个性等。然而在我们C计算机语言中的类也存在继承,我们将作为“父亲”的类称为父类,将作为“孩子”的类称为子类,父…...
Java并发-AQS框架原理解析与实现类详解
什么是AQS? AQS(AbstractQueuedSynchronizer)是Java并发包(JUC)的核心基础框架,它为构建锁和同步器提供了高效、灵活的底层支持。本文将从设计原理、核心机制及典型实现类三个维度展开,帮助读者…...
【FFmpeg从入门到精通】第一章-FFmpeg简介
1 FFmpeg的定义 FFmpeg既是一款音视频编解码工具,同时也是一组音视频编解码开发套件,作为编解码开发套件,它为开发者提供了丰富的音视频处理的调用接口。 FFmpeg提供了多种媒体格式的封装和解封装,包括多种音视频编码、多种协议…...
Mac屏幕共享怎么使用?
Mac电脑要实现远程桌面连接到的工功能,可以使用其自带的屏幕共享功能。Mac屏幕共享能从一台Mac电脑远程控制另一台Mac电脑,并且无需下载第三方远程控制软件。下面,将为您介绍Mac远程桌面连接在哪,以及使用方法。 步骤 1. Mac的远…...
探索亮数据Web Unlocker API:让谷歌学术网页科研数据 “触手可及”
本文目录 一、引言二、Web Unlocker API 功能亮点三、Web Unlocker API 实战1.配置网页解锁器2.定位相关数据3.编写代码 四、Web Scraper API技术亮点 五、SERP API技术亮点 六、总结 一、引言 网页数据宛如一座蕴藏着无限价值的宝库,无论是企业洞察市场动态、制定…...
【后端】【python】利用反射器----动态设置装饰器
📘 Python 装饰器进阶指南 一、装饰器本质 ✅ 本质概念 Python 装饰器的本质是 函数嵌套 返回函数,它是对已有函数的增强,不修改原函数代码,使用语法糖 decorator 实现包裹效果。 def my_decorator(func):def wrapper(*args, …...
Oracle 中的 NOAUDIT CREATE SESSION 命令详解
Oracle 中的 NOAUDIT CREATE SESSION 命令详解 NOAUDIT CREATE SESSION 是 Oracle 数据库中用于取消对用户登录会话审计的命令,它与 AUDIT CREATE SESSION 命令相对应。 一、基本语法 NOAUDIT CREATE SESSION [BY user1 [, user2]... | BY [SESSION | ACCESS]] …...
《Chronos: Learning the Language of Time Series》
全文摘要 本文提出了Chronos,一个简单而有效的预训练概率时间序列模型框架。Chronos通过缩放和量化将时间序列值标记化为固定词汇,并利用现有的基于变换器的语言模型架构进行训练。我们在多个公开数据集和合成数据集上预训练了Chronos模型,并…...
git UserInterfaceState.xcuserstate 文件频繁更新
1> 退出 Xcdoe,打开终端(Terminal),进入到你的项目目录下。 2> 在终端键入 git rm --cached <YourProjectName>.xcodeproj/project.xcworkspace/xcuserdata/<YourUsername>.xcuserdatad/UserInterfaceState.x…...
Day92 | 灵神 | 二叉树 路径总和
Day92 | 灵神 | 二叉树 路径总和 112.路径总和 112. 路径总和 - 力扣(LeetCode) 思路: 1.递归函数意义 如果在根节点为t的树中可以找到长度为target的路径就返回true,找不到就返回false 2.参数和返回值 bool tra(TreeNode …...
数据集 handpose_x_plus 3D RGB 三维手势 - 打篮球 场景 play basketball
数据集 handpose 相关项目地址:https://github.com/XIAN-HHappy/handpose_x_plus 样例数据下载地址:数据集handpose-x-plus3DRGB三维手势-打篮球场景playbasketball资源-CSDN文库...
GitLab本地安装指南
当前GitLab的最新版是v17.10,安装地址:https://about.gitlab.com/install/。当然国内也可以安装极狐GitLab版本,极狐GitLab 是 GitLab 中国发行版(JH)。极狐GitLab支持龙蜥,欧拉等国内的操作系统平台。安装…...