前言
在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》,结合我自己的工作学习经历,我准备写一个音视频系列blog。C/C++是音视频必备编程语言,我准备用几篇文章来快速回顾C++。本文是音视频系列blog的其中一个, 对应的要学习的内容是:快速回顾C++常见语句,类入门,变量和基本类型,字符串、向量和数组,函数。
音视频系列blog
音视频系列blog: 点击此处跳转查看.
目录
1 C++入门
1.1 C++第一个程序
C++的第一个程序也肯定是输出“Hello World”,程序中永恒的经典!!!
C++中输出"Hello, world!"时,可以使用标准的输出流std::cout
。以下是一个简单的示例程序:
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
return 0;
}
在这个程序中,#include <iostream>
包含了输入输出流的相关定义。std::cout
用于输出文本,<<
运算符用于将文本插入到输出流中。std::endl
用于插入换行符并刷新输出流。最后,return 0;
表示程序成功地执行并返回。编译并运行这个程序,将在控制台上看到输出的"Hello, world!"。
1.2 C++输入输出
在C++中,可以使用标准输入输出流来实现输入和输出操作。标准输入流是std::cin
,用于从用户获取输入,而标准输出流是std::cout
,用于向用户显示输出。以下是一个简单的示例,演示如何在C++中进行输入和输出操作:
#include <iostream>
#include <string>
int main() {
// 输出欢迎信息
std::cout << "欢迎来到输入输出示例程序!" << std::endl;
// 输入一个整数
int num;
std::cout << "请输入一个整数: ";
std::cin >> num;
// 输入一个浮点数
double floatingNum;
std::cout << "请输入一个浮点数: ";
std::cin >> floatingNum;
// 输入一个字符串
std::string text;
std::cout << "请输入一段文本: ";
std::cin.ignore(); // 忽略前面的换行符
std::getline(std::cin, text);
// 输出输入的内容
std::cout << "您输入的整数是: " << num << std::endl;
std::cout << "您输入的浮点数是: " << floatingNum << std::endl;
std::cout << "您输入的文本是: " << text << std::endl;
return 0;
}
在这个示例中,首先使用std::cout
输出欢迎信息。然后使用std::cin
输入整数、浮点数和字符串。请注意,当输入字符串时,使用std::cin.ignore()
来忽略前面的换行符,然后使用std::getline()
获取整行输入。编译并运行这个程序,你将能够输入整数、浮点数和字符串,并看到它们被正确地输出到控制台。
1.3 选择语句
在C++中,可以使用选择语句来根据条件执行不同的代码块。常用的选择语句包括if
语句、if-else
语句和switch
语句。以下是这些选择语句的示例:
if
语句:用于在条件为真时执行一段代码。
#include <iostream>
int main() {
int num;
std::cout << "请输入一个整数: ";
std::cin >> num;
if (num > 0) {
std::cout << "您输入的数是正数。" << std::endl;
}
return 0;
}
if-else
语句:用于在条件为真时执行一个代码块,否则执行另一个代码块。
#include <iostream>
int main() {
int num;
std::cout << "请输入一个整数: ";
std::cin >> num;
if (num > 0) {
std::cout << "您输入的数是正数。" << std::endl;
} else {
std::cout << "您输入的数不是正数。" << std::endl;
}
return 0;
}
switch
语句:用于根据表达式的值从一组可能值中选择执行的代码块。
#include <iostream>
int main() {
char grade;
std::cout << "请输入您的等级(A/B/C/D/F): ";
std::cin >> grade;
switch (grade) {
case 'A':
std::cout << "优秀!" << std::endl;
break;
case 'B':
std::cout << "良好。" << std::endl;
break;
case 'C':
std::cout << "中等。" << std::endl;
break;
case 'D':
std::cout << "及格。" << std::endl;
break;
case 'F':
std::cout << "不及格。" << std::endl;
break;
default:
std::cout << "无效的等级。" << std::endl;
break;
}
return 0;
}
这些示例展示了如何在C++中使用选择语句来根据条件执行不同的代码块。根据实际情况,可以结合使用这些语句来构建更复杂的逻辑。
1.4 循环语句
在C++中,可以使用循环语句来重复执行一段代码块,以实现迭代操作。常用的循环语句包括for
循环、while
循环和do-while
循环。以下是这些循环语句的示例:
for
循环:用于指定一个初始值、一个循环条件和一个迭代步骤,然后在满足循环条件时重复执行代码块。
#include <iostream>
int main() {
for (int i = 1; i <= 5; ++i) {
std::cout << "循环迭代 " << i << std::endl;
}
return 0;
}
while
循环:在满足指定条件时重复执行代码块。
#include <iostream>
int main() {
int count = 1;
while (count <= 5) {
std::cout << "循环迭代 " << count << std::endl;
++count;
}
return 0;
}
do-while
循环:首先执行一次代码块,然后在满足指定条件时重复执行。
#include <iostream>
int main() {
int count = 1;
do {
std::cout << "循环迭代 " << count << std::endl;
++count;
} while (count <= 5);
return 0;
}
这些示例展示了如何在C++中使用循环语句来重复执行代码块。循环语句非常有用,可以用来处理大量的数据、执行特定次数的操作等等。根据实际需求,可以选择适合的循环类型来实现你的目标。
1.5 类入门
1.5.1 类简介
在C++中,类是一种用于封装数据和操作的重要工具,它允许定义一种新的数据类型。类可以包含属性(成员变量)和方法(成员函数),从而使得你能够更好地组织和管理代码。以下是一个简单的C++类的入门示例:
#include <iostream>
#include <string>
// 定义一个名为Person的类
class Person {
public:
// 成员变量
std::string name;
int age;
// 成员函数(方法)
void introduce() {
std::cout << "我叫" << name << ",今年" << age << "岁。" << std::endl;
}
};
int main() {
// 创建一个Person对象
Person person1;
// 设置成员变量的值
person1.name = "Alice";
person1.age = 30;
// 调用成员函数
person1.introduce();
return 0;
}
在上面的示例中,定义了一个名为Person
的类,它有两个成员变量 name
和 age
,以及一个成员函数 introduce()
用于打印个人信息。在main()
函数中,创建了一个Person
对象person1
,设置其成员变量的值,然后调用introduce()
方法展示个人信息。
这只是一个简单的类示例,C++类的功能远远不止于此。可以在类中添加更多成员变量和成员函数,甚至可以使用访问控制符(public
、private
、protected
)来限制成员的访问权限。通过使用类,可以更好地组织代码、封装数据,使代码更加模块化和易于维护(这些内容后面会详细介绍)。
1.5.2 面向过程编程VS面向对象编程
面向过程编程
面向过程编程是一种编程范式,它将程序设计看作是一系列按照特定顺序执行的操作步骤的集合,强调解决问题时需要对问题进行分解,然后逐步地定义操作步骤以达到最终目标。这种编程风格关注的是“做什么”以及“怎么做”,注重操作和流程的控制。
想象一下你正在制作一份烹饪食谱。面向过程编程类似于你将每个烹饪步骤详细列出,从准备材料、切菜、炒菜,到最后的上菜。你会按照特定的顺序,依次执行每个步骤,最终得到一道美味的菜肴。在面向过程编程中,你会将问题分解为一系列可执行的步骤,每个步骤执行特定的操作。
面向过程编程的主要特点包括:
- 过程和流程控制: 程序由一系列的过程(函数或方法)组成,每个过程执行特定的任务。程序流程由顺序结构、条件语句和循环等控制。
- 重用性较低: 在不同的程序中,可能需要重复编写相似的过程,导致代码重复。
- 可读性强: 由于操作步骤清晰可见,理解起来相对容易。
- 适用于简单任务: 面向过程编程适合处理一些相对简单的任务,如数值计算、文件处理等。
虽然面向过程编程在某些情况下非常有效,但在处理大型、复杂的软件系统时,面向过程的代码可能会变得难以维护和扩展。这就引入了面向对象编程(OOP)等其他编程范式,以更好地处理复杂性和提高代码的可维护性。
总之,面向过程编程是一种将问题分解为一系列操作步骤的编程方式,适合处理相对简单的任务,但可能在复杂情况下显得不够灵活。
面向对象编程
面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序设计看作是一系列相互协作的对象,强调将数据和操作组合在一起,以模拟真实世界中的事物和关系。这种编程风格注重将问题分解为对象,每个对象代表了一个实体,具有数据和可执行的方法。
上面这段话是很专业吧, 不理解的话我举两个生活中的例子来解释。
- 案例1
用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
蛋炒饭制作步骤:准备胡萝卜丁和腊肉丁,准备米饭,起锅烧油,炒鸡蛋,加入米饭胡萝卜丁腊肉丁一起炒,添加调料,炒好之后装盘。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
蛋炒饭的好处就是入味均匀,吃起来香。如果你不爱吃胡萝卜丁,只爱吃腊肉丁的话,那么就需要全部倒掉,重新做一份腊肉炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
盖浇饭的好处就是"菜"“饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,“饭” 和"菜"的耦合度比较低。蛋炒饭将"蛋”“饭"搅和在一起,想换"蛋”"饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
- 案例2
假设你正在制作一个电子商务网站,我们可以使用类比来说明面向对象编程的概念:
- 类和对象:
想象你正在建立一个“商品”(Product)类,这个类描述了商品的特性,比如名称、价格和描述等。每个商品都是这个类的一个实例,就像你在网站上看到的每个商品都是具体的物品。 - 封装:
在“商品”类中,你可以将商品的具体细节封装起来,比如将价格设为私有成员变量,这样外部的代码就不能直接访问和修改价格。你可以提供公共的方法(比如“getPrice()”)来访问价格,从而控制对内部数据的访问。 - 继承:
假设你还有不同类型的商品,如“书籍”(Book)和“电子产品”(Electronics)。你可以从“商品”类派生出这些子类,每个子类可以继承“商品”类的通用属性(如名称和描述),并且还可以有自己特有的属性(如作者对于“书籍”类)。这样,你可以更好地组织和管理不同类型的商品。 - 多态:
在网站上,你可能有一个“购物车”(ShoppingCart)类,它可以存储用户选购的商品。你可以编写一个函数,接受不同类型的商品对象作为参数,然后将它们添加到购物车中。因为这些商品都是“商品”类的子类,多态性使得你可以轻松地处理不同类型的商品,而不必关心具体是哪个子类。 - 抽象类和纯虚函数:
假设你想要实现一个优惠券系统,其中某些商品可以应用优惠券,而另一些则不行。你可以创建一个名为“可打折商品”(DiscountableProduct)的抽象类,其中定义一个纯虚函数“applyDiscount()”。然后,“书籍”和“电子产品”等子类可以继承这个抽象类并实现自己的“applyDiscount()”函数,以决定如何应用折扣。
面向对象编程的主要特点包括:
- 对象: 程序由一组对象组成,每个对象都是一个实体,具有属性(数据)和方法(操作)。
- 封装: 将数据和操作封装在对象内部,隐藏了内部细节,只暴露必要的接口。这有助于降低代码的耦合度,增强了代码的模块化和可维护性。
- 继承: 允许一个对象(子类)继承另一个对象(父类)的属性和方法,促进了代码的重用和层次结构。
- 多态: 不同的对象可以对相同的消息做出不同的响应,通过多态性可以实现灵活且可扩展的设计。
- 抽象: 通过抽象类和接口定义通用的特征和行为,可以让对象具备共同的属性和方法。
面向对象编程适用于处理复杂的软件系统,能够更好地管理和组织代码,使得代码更加可维护、可扩展,并且能够更好地模拟和反映现实世界中的关系。虽然面向对象编程可能需要更多的抽象和设计工作,但它为构建大型、复杂的应用程序提供了强大的工具。
2 变量和基本类型
2.1 基本内置类型
C++中的基本内置数据类型用于存储不同种类的数据。这些基本类型提供了不同的数据大小和精度,以满足各种编程需求。以下是C++中常见的基本内置数据类型:
- 整数类型:
int
:整数类型,通常占用4个字节。short
:短整数类型,通常占用2个字节。long
:长整数类型,通常占用4或8个字节,取决于系统平台。long long
:更长的整数类型,通常占用8个字节。
- 无符号整数类型:
unsigned int
:无符号整数类型,用于表示非负整数。unsigned short
:无符号短整数类型。unsigned long
:无符号长整数类型。unsigned long long
:无符号更长的整数类型。
- 浮点数类型:
float
:单精度浮点数,通常占用4个字节。double
:双精度浮点数,通常占用8个字节。long double
:更长的双精度浮点数,字节可能更多,取决于系统平台。
- 字符类型:
char
:字符类型,通常占用1个字节,用于存储单个字符。signed char
:有符号字符类型。unsigned char
:无符号字符类型。
- 布尔类型:
bool
:布尔类型,用于表示真或假(true或false)值。
- 空类型:
void
:空类型,通常用于函数返回值为空或指针类型时。
此外,C++还提供了一些扩展的整数类型,如int8_t
、uint16_t
等,它们有固定的大小,不受系统平台影响,位于头文件<cstdint>
中。
类型转换
在C++中,类型转换是将一个数据类型的值转换为另一个数据类型的过程。C++提供了多种类型转换方式,包括隐式类型转换和显式类型转换。以下是常见的类型转换方式:
- 隐式类型转换(自动类型转换): 在一些情况下,C++会自动进行类型转换,以满足运算要求或函数参数匹配。例如,整数和浮点数之间的算术运算会导致隐式类型转换。这种转换由编译器自动完成,无需程序员干预。
int num = 5;
double result = num + 2.5; // 隐式将整数转换为双精度浮点数
- 显式类型转换(强制类型转换): 在某些情况下,可能需要显式地告诉编译器进行类型转换。C++提供了三种显式类型转换运算符:
static_cast
:用于较小范围内的类型转换,如数值之间的转换。dynamic_cast
:主要用于类层次结构中的向下转型,需要在运行时检查。const_cast
:用于添加或去除const限定符。reinterpret_cast
:进行底层二进制的转换,如指针类型之间的转换。
int num1 = 10;
double num2 = static_cast<double>(num1); // 将整数转换为双精度浮点数
const int x = 5;
int* ptr = const_cast<int*>(&x); // 去除const限定符
double* doublePtr = reinterpret_cast<double*>(&num1); // 重新解释指针类型
显式类型转换需要谨慎使用,因为它可能导致数据丢失或未定义行为。务必确保类型转换是安全和合理的。
字面值常量
C++中的字面值常量是直接在代码中使用的固定值,它们表示了不同类型的数据,如整数、浮点数、字符、字符串等。字面值常量在代码中直接写出,无需进行计算或处理。以下是C++中常见的字面值常量类型:
- 整数字面值: 表示整数的值,可以是十进制、八进制、十六进制等形式。
- 十进制:
42
- 八进制:
052
(以0开头) - 十六进制:
0x2A
或0X2A
(以0x或0X开头)
- 十进制:
- 浮点数字面值: 表示浮点数的值,可以是小数形式或指数形式。
- 小数形式:
3.14
- 指数形式:
6.022e23
(表示6.022乘以10的23次方)
- 小数形式:
- 字符字面值: 表示一个字符,使用单引号括起来。
- 字符:
'A'
- 转义字符:
'\n'
(表示换行符)、'\t'
(表示制表符)等
- 字符:
- 字符串字面值: 表示一个字符串,使用双引号括起来。
- 字符串:
"Hello, world!"
- 字符串:
- 布尔字面值: 表示布尔值,只有两个值:
true
和false
。- 布尔值:
true
或false
- 布尔值:
- 空指针字面值: 表示空指针,使用关键字
nullptr
。- 空指针:
nullptr
- 空指针:
字面值常量可以直接在代码中使用,例如:
int num = 42;
double pi = 3.14159;
char letter = 'A';
std::string message = "Hello, world!";
bool isTrue = true;
字面值常量提供了一种简便的方式来表示特定的数据值,使代码更加直观和易读。
2.2 变量
在C++中,变量是用于存储数据的名称化内存位置。每个变量都有一个数据类型,用于指定变量可以存储的数据种类,以及所占用的内存空间。变量在程序中用于存储、操作和传递数据。
以下是在C++中声明和使用变量的示例:
#include <iostream>
int main() {
// 声明变量
int age;
double weight;
char initial = 'J';
std::string name = "John";
// 赋值
age = 25;
weight = 70.5;
// 使用变量
std::cout << "姓名:" << name << std::endl;
std::cout << "年龄:" << age << " 岁" << std::endl;
std::cout << "体重:" << weight << " 公斤" << std::endl;
std::cout << "首字母:" << initial << std::endl;
return 0;
}
在上面的示例中,我们首先声明了不同类型的变量(age
、weight
、initial
、name
),然后赋值给这些变量,最后在控制台输出了变量的值。
值得注意的是,变量名必须遵循一定的规则:
- 变量名由字母、数字和下划线组成,且不能以数字开头。
- 变量名区分大小写,例如,
myVariable
和myvariable
被视为不同的变量。 - 变量名不能与C++的关键字(例如
int
、double
、if
等)相同。
在C++中,变量的作用域指的是变量在代码中可见的范围。变量可以具有局部作用域(在特定代码块内可见)或全局作用域(在整个程序中可见)。
2.3 复合类型
在C++中,复合类型是由基本数据类型组合而成的数据类型,可以在一个变量中存储多个值或数据。C++提供了几种常见的复合类型,包括数组、指针、引用和结构体(struct)。
以下是这些复合类型的简要介绍:
- 数组(Array): 数组是一组相同类型的元素的集合,通过索引访问各个元素。数组可以在声明时指定大小,也可以使用动态分配的方式创建。
int numbers[5]; // 声明一个包含5个整数的数组
double temperatures[10]; // 声明一个包含10个双精度浮点数的数组
// 初始化数组
int primes[] = {2, 3, 5, 7, 11};
- 指针(Pointer): 指针是一个变量,用于存储另一个变量的内存地址。指针允许你直接访问变量所在的内存位置。
int num = 42;
int* ptr; // 声明一个整数指针
ptr = # // 指针指向num的地址
// 访问指针指向的值
int value = *ptr; // value将等于42
- 引用(Reference): 引用是变量的别名,允许你通过不同的名称访问同一个变量。引用通常用于函数参数传递和函数返回值。
int num = 10;
int& ref = num; // 声明一个整数引用,它是num的别名
ref = 20; // 修改ref,也会修改num的值
- 结构体(Struct): 结构体允许你将不同类型的数据组合在一起,形成一个自定义的数据类型。结构体中的成员可以是不同的数据类型。
struct Person {
std::string name;
int age;
};
Person person1; // 声明一个Person类型的变量
person1.name = "Alice";
person1.age = 30;
复合类型允许你更灵活地组织数据,以及在程序中创建更复杂的数据结构。
2.4 const限定符
在C++中,const
是一个重要的限定符,用于指定变量或函数参数的值不能被修改。使用 const
可以增加代码的安全性和可维护性,防止意外的数据修改。const
可以应用于不同的上下文,包括变量、函数参数和函数返回值。
以下是 const
在不同上下文中的用法:
- 常量变量: 使用
const
修饰变量,表示该变量的值在初始化后不可修改。
const int age = 25;
const double pi = 3.14159;
- 常量指针: 使用
const
修饰指针,表示指针指向的值不可修改。
int num = 42;
const int* ptr = # // ptr 是指向常量的指针,不可通过 ptr 修改 num 的值
int x = 10;
int y = 20;
int* const p = &x; // p 是常量指针,指向 x,但 p 的值不可修改
- 常量引用: 使用
const
修饰引用,表示通过引用不可修改原始变量的值。
int num = 10;
const int& ref = num; // ref 是常量引用,不可通过 ref 修改 num 的值
- 函数参数: 使用
const
修饰函数参数,表示函数内部不会修改参数的值。
void printValue(const int value) {
// value 是常量,不可修改
std::cout << "Value: " << value << std::endl;
}
- 函数返回值: 使用
const
修饰函数返回值,表示返回的值不可修改。
const int getNumber() {
return 42;
}
const
限定符可以增加代码的清晰度,防止无意的数据修改,以及在函数中传递数据时确保数据的安全性。使用 const
有助于编写更健壮、可维护的代码。
2.5 处理类型
在C++中,有几种用于处理类型的工具,包括类型别名、auto
类型说明符和 decltype
类型指示符。这些工具有助于简化代码、提高可读性和灵活性。
- 类型别名(Type Alias): 类型别名允许你为一个已有类型赋予一个新的名字,使代码更加清晰和可读。可以使用关键字
using
或typedef
来创建类型别名。
using Distance = double; // 创建一个类型别名 Distance,表示距离(双精度浮点数)
typedef int ItemCount; // 创建一个类型别名 ItemCount,表示物品数量(整数)
auto
类型说明符:auto
关键字允许编译器自动推断变量的类型,根据初始化表达式的类型来确定变量的类型。这在简化代码和处理复杂类型时特别有用。
auto x = 42; // x 的类型将被推断为 int
auto name = "John"; // name 的类型将被推断为 const char*
decltype
类型指示符:decltype
关键字用于获得表达式的类型,可以在不实际执行表达式的情况下确定类型。通常用于从表达式中推断返回类型或声明具有相同类型的变量。
int a = 10;
decltype(a) b = 20; // b 的类型将被推断为 int
double func(double x) {
return x * x;
}
decltype(func(5.0)) result; // result 的类型将被推断为 double
3 字符串、向量和数组
3.1 命名空间的 using 声明
在C++中,using
声明是一种用于简化命名空间中成员的访问的方式。它允许你在代码中引入命名空间中的一个或多个成员,从而可以直接使用这些成员,而无需使用完整的命名空间限定符。
使用 using
声明可以减少代码中的重复,提高可读性,特别是在需要频繁访问某个命名空间的成员时。
以下是使用 using
声明的示例:
#include <iostream>
// 命名空间 A
namespace A {
void foo() {
std::cout << "函数 foo() 在命名空间 A 中" << std::endl;
}
}
// 命名空间 B
namespace B {
void bar() {
std::cout << "函数 bar() 在命名空间 B 中" << std::endl;
}
}
int main() {
using A::foo; // 使用 using 声明引入命名空间 A 中的 foo() 函数
foo(); // 可以直接使用 foo(),无需使用 A::foo()
// B::bar(); // 如果需要使用 B::bar(),则需要使用完整的命名空间限定符
return 0;
}
在上面的示例中,我们使用 using A::foo;
声明引入了命名空间 A
中的 foo()
函数。这样,在 main()
函数中就可以直接使用 foo()
,无需使用 A::foo()
。
需要注意的是,using
声明引入的成员可能会引起命名冲突,特别是在多个命名空间中有相同名称的成员时。在使用 using
声明时,要谨慎处理,以避免意外的问题。
3.2 标准库类型 string
C++定义和初始化 string
对象
在C++中,std::string
是用于处理字符串的类,它提供了许多功能来操作和管理字符串数据。下面是定义和初始化 string
对象的几种方式:
- 默认构造函数: 创建一个空的
string
对象。
#include <string>
std::string str; // 默认构造函数创建一个空字符串
- 使用字符串字面值初始化:
std::string greeting = "Hello, world!";
- 拷贝构造函数: 从另一个
string
对象复制内容。
std::string original = "Hello";
std::string copy = original; // 使用拷贝构造函数复制内容
- 使用
=
运算符:
std::string name;
name = "Alice"; // 使用 = 运算符赋值
- 使用
+
运算符连接字符串:
std::string first = "Hello, ";
std::string second = "world!";
std::string combined = first + second; // 连接两个字符串
string
对象上的操作
std::string
类提供了许多成员函数和运算符来对字符串进行操作,包括插入、连接、查找、比较等。以下是一些常见的操作:
#include <iostream>
#include <string>
int main() {
std::string greeting = "Hello, world!";
// 获取字符串长度
std::cout << "Length: " << greeting.length() << std::endl;
// 获取字符
char firstChar = greeting[0];
// 字符串连接
std::string name = "Alice";
std::string message = "Welcome, " + name;
// 查找子字符串
size_t found = message.find("Alice");
// 替换子字符串
message.replace(found, 5, "Bob");
// 输出结果
std::cout << message << std::endl;
return 0;
}
处理 string
对象中的字符
std::string
提供了多种方法来访问和处理字符串中的字符,包括使用下标访问、迭代器等。
#include <iostream>
#include <string>
int main() {
std::string str = "Hello";
// 使用下标访问字符
char firstChar = str[0];
// 使用迭代器遍历字符
for (std::string::iterator it = str.begin(); it != str.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
std::string
提供了丰富的函数和方法来处理字符串,可以根据需要选择适合的操作来操作和处理字符串数据。
3.3 标准库类型 vector
C++定义和初始化 vector
对象
在C++中,std::vector
是一个动态数组,可以在运行时根据需要自动调整大小。它是一个标准模板库(STL)容器,用于存储一组具有相同数据类型的元素。下面是定义和初始化 vector
对象的几种方式:
- 默认构造函数: 创建一个空的
vector
对象。
#include <vector>
std::vector<int> numbers; // 默认构造函数创建一个空的整数向量
- 指定大小并初始化:
std::vector<double> prices(10, 0.0); // 创建一个包含10个双精度浮点数的向量,并初始化为0.0
- 使用初始化列表:
std::vector<std::string> fruits = {"apple", "banana", "orange"};
向 vector
对象中添加元素
可以使用 push_back()
方法将元素添加到 vector
对象的末尾。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
其他 vector
操作
std::vector
提供了许多其他操作来处理向量,例如访问元素、插入和删除元素、获取向量大小等。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 访问元素
int firstElement = numbers[0];
int lastElement = numbers.back();
// 插入元素
numbers.insert(numbers.begin() + 2, 6); // 在位置2插入元素6
// 删除元素
numbers.erase(numbers.begin() + 3); // 删除位置3的元素
// 获取向量大小
size_t size = numbers.size();
// 遍历向量
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
std::vector
提供了灵活的动态数组功能,可以根据需要添加、删除和操作元素。
3.4 迭代器
在C++中,迭代器(Iterator)是一种用于遍历容器中元素的抽象概念,它允许你访问容器中的元素,而无需直接操作容器的内部实现。迭代器提供了一种通用的方式来遍历不同类型的容器,如数组、std::vector
、std::list
等。
使用迭代器,你可以在容器中依次访问元素,执行操作或获取数据。以下是迭代器的基本用法:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用迭代器遍历向量
std::vector<int>::iterator it; // 定义迭代器
for (it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
在上面的示例中,numbers.begin()
返回指向向量首元素的迭代器,numbers.end()
返回指向向量末尾下一个位置的迭代器。通过 ++it
将迭代器移动到下一个位置,*it
可以访问当前位置的元素。
C++还引入了C++11起的新范围基于循环(Range-Based Loop)语法,更方便地使用迭代器:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用范围基于循环遍历向量
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
3.5 一维数组
C++中的一维数组是一组相同类型的元素的集合,这些元素按照一定的顺序存储在内存中。数组提供了一种便捷的方式来存储和操作多个相似类型的数据。以下是一维数组的基本用法:
#include <iostream>
int main() {
// 声明一个整数数组,包含5个元素
int numbers[5];
// 初始化数组元素
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
// 访问数组元素
std::cout << "第一个元素:" << numbers[0] << std::endl;
std::cout << "第三个元素:" << numbers[2] << std::endl;
return 0;
}
上述代码定义了一个包含5个整数元素的数组 numbers
,并初始化了数组的各个元素。你可以使用下标访问数组中的元素,下标从0开始,即第一个元素的下标是0。
另一种初始化数组的方式是使用大括号初始化列表:
int numbers[] = {10, 20, 30, 40, 50};
数组的大小在声明时指定,一旦确定,大小就不可更改。如果需要存储更多的元素,就需要声明一个更大或者动态分配内存的数组。
数组还可以用于循环遍历,执行各种操作,以及在算法和数据处理中使用。需要注意的是,C++中的数组越界访问会导致未定义行为,因此在使用数组时要确保下标在合法范围内。
3.6 二维数组
C++中的二维数组是一个表格状的数据结构,它由行和列组成,每个单元格存储一个值,所有值的类型都相同。二维数组可以看作是数组的数组,用于表示矩阵、网格等具有二维结构的数据。
以下是二维数组的基本用法:
#include <iostream>
int main() {
// 声明一个3行4列的整数二维数组
int matrix[3][4];
// 初始化二维数组元素
matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[0][3] = 4;
matrix[1][0] = 5;
matrix[1][1] = 6;
matrix[1][2] = 7;
matrix[1][3] = 8;
matrix[2][0] = 9;
matrix[2][1] = 10;
matrix[2][2] = 11;
matrix[2][3] = 12;
// 访问二维数组元素
std::cout << "第一行第二列的元素:" << matrix[0][1] << std::endl;
std::cout << "第二行第三列的元素:" << matrix[1][2] << std::endl;
return 0;
}
上述代码定义了一个3行4列的整数二维数组 matrix
,并初始化了数组的各个元素。你可以使用行索引和列索引来访问二维数组中的元素。
另一种初始化二维数组的方式是使用大括号初始化列表:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
二维数组可以用于处理具有行列结构的数据,如游戏地图、图像数据、矩阵运算等。需要注意的是,二维数组的行数和列数在声明时必须指定,且不可更改。
3.7 指针和数组
在C++中,指针和数组之间有着紧密的关系,因为数组名本质上就是一个指向数组首元素的指针。这种关系使得指针可以用于遍历数组、访问数组元素以及进行数组操作。
以下是指针和数组的一些基本用法:
#include <iostream>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// 声明一个指向整数的指针
int* ptr = numbers;
// 使用指针访问数组元素
std::cout << "第一个元素:" << *ptr << std::endl; // 输出 10
// 使用指针遍历数组
for (int i = 0; i < 5; ++i) {
std::cout << *ptr << " ";
ptr++; // 移动指针到下一个元素
}
return 0;
}
在上面的示例中,我们声明了一个指向整数的指针 ptr
,将它指向了数组 numbers
的首元素。通过 *ptr
可以访问当前指针所指向的数组元素。通过移动指针的位置,我们可以遍历整个数组。
另外,数组名本身就是一个指向数组首元素的指针,因此以下代码也是合法的:
int numbers[5] = {10, 20, 30, 40, 50};
int* ptr = numbers;
std::cout << "第一个元素:" << *numbers << std::endl; // 输出 10
需要注意的是,指针和数组的关系不仅限于一维数组,对于多维数组同样适用。指针和数组的组合在C++中经常用于实现复杂的数据操作和算法。
4 函数
4.1 函数基础
在C++中,函数是一段代码块,用于执行特定的任务或完成特定的操作。函数将一系列操作封装在一个单独的单元中,可以在程序中多次调用,提高了代码的可读性、可维护性和重用性。
以下是定义、调用和使用函数的基本用法:
#include <iostream>
// 函数声明
int add(int a, int b);
int main() {
// 函数调用
int result = add(5, 3);
std::cout << "结果:" << result << std::endl;
return 0;
}
// 函数定义
int add(int a, int b) {
return a + b;
}
在上述代码中,我们定义了一个名为 add
的函数,在 main
函数中调用了它,然后将其返回值输出到控制台。函数的定义包括函数名、参数列表、返回类型以及函数体。函数的声明告诉编译器函数的存在和签名,从而允许在函数调用之前使用它。
除了上述基本用法,C++中的函数还可以具有默认参数、函数重载、递归调用、函数指针等特性。例如:
#include <iostream>
// 带有默认参数的函数
int multiply(int a, int b = 2);
// 函数重载
double add(double a, double b);
int add(int a, int b, int c);
int main() {
int result = multiply(5); // 使用默认参数
std::cout << "结果:" << result << std::endl;
double sum1 = add(3.5, 2.5);
int sum2 = add(1, 2, 3);
return 0;
}
int multiply(int a, int b) {
return a * b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
函数是C++中非常重要的概念,它使得程序模块化、可维护和可扩展。
4.2 参数传递
在C++中,参数传递是指在函数调用过程中,将数据传递给函数的过程。C++支持不同的参数传递方式,包括传值参数、传引用参数、const 形参和实参、数组形参,以及含有可变形参的函数。
传值参数(Pass by Value)
传值参数是将实参的值复制一份传递给函数的形参。在函数内部,对形参的修改不会影响到原始的实参。
#include <iostream>
void modifyValue(int x) {
x = 10; // 修改形参的值,不会影响原始值
}
int main() {
int num = 5;
modifyValue(num);
std::cout << "num: " << num << std::endl; // 输出:num: 5
return 0;
}
传引用参数(Pass by Reference)
传引用参数是将实参的引用传递给函数的形参,这样函数内部对形参的修改会影响到原始的实参。
#include <iostream>
void modifyReference(int& x) {
x = 10; // 修改形参的值,同时也修改了原始值
}
int main() {
int num = 5;
modifyReference(num);
std::cout << "num: " << num << std::endl; // 输出:num: 10
return 0;
}
const 形参和实参
使用 const
修饰形参可以保证函数内部不会修改实参的值。
#include <iostream>
void printValue(const int x) {
// x = 10; // 错误,不允许修改 x
std::cout << "Value: " << x << std::endl;
}
int main() {
int num = 5;
printValue(num);
return 0;
}
数组形参
函数可以接受数组作为参数,但在传递数组时,通常需要同时传递数组的大小。
#include <iostream>
void printArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
printArray(numbers, 5);
return 0;
}
含有可变形参的函数
C++11 引入了可变参数模板,允许函数接受不定数量的参数。
#include <iostream>
#include <stdarg.h>
double average(int count, ...) {
va_list args;
va_start(args, count);
double sum = 0;
for (int i = 0; i < count; ++i) {
sum += va_arg(args, int);
}
va_end(args);
return sum / count;
}
int main() {
std::cout << "Average: " << average(4, 10, 20, 30, 40) << std::endl;
return 0;
}
在上面的例子中,average
函数可以接受任意数量的参数,并计算它们的平均值。
以上是关于不同参数传递方式的示例。根据实际需求,可以选择合适的参数传递方式来实现所需的功能。
4.3 返回类型和 return 语句
在C++中,函数的返回类型和 return
语句用于确定函数的返回值以及在函数中如何返回这个值。函数可以分为无返回值函数和有返回值函数,并且还可以返回数组指针。
无返回值函数(Void Functions)
无返回值函数指的是函数不返回任何值,通常用于执行一系列操作而不产生结果。
#include <iostream>
void printMessage() {
std::cout << "Hello, World!" << std::endl;
}
int main() {
printMessage(); // 调用无返回值函数
return 0;
}
有返回值函数
有返回值函数指的是函数返回一个特定类型的值。函数的返回类型在函数声明和定义中指定。
#include <iostream>
int add(int a, int b) {
return a + b;
}
double divide(double x, double y) {
if (y != 0) {
return x / y;
} else {
std::cout << "除数不能为零" << std::endl;
return 0; // 返回默认值
}
}
int main() {
int sum = add(5, 3);
std::cout << "Sum: " << sum << std::endl;
double result = divide(10.0, 2.0);
std::cout << "Result: " << result << std::endl;
return 0;
}
返回数组指针
函数可以返回数组指针,但是要注意数组指针的声明和使用。
#include <iostream>
int* createArray(int size) {
int* arr = new int[size];
for (int i = 0; i < size; ++i) {
arr[i] = i * 2;
}
return arr;
}
int main() {
int* ptr = createArray(5);
for (int i = 0; i < 5; ++i) {
std::cout << ptr[i] << " ";
}
delete[] ptr; // 释放动态分配的内存
return 0;
}
在上面的示例中,createArray
函数返回一个指向动态分配的整数数组的指针。注意在使用完毕后要使用 delete[]
释放内存。
4.4 函数重载
C++中的函数重载(Function Overloading)是指在同一个作用域内,可以定义具有相同名称但不同参数列表的多个函数。函数重载允许你根据不同的参数类型和数量来调用不同的函数,提供了更灵活的函数调用方式。
函数重载的规则如下:
- 函数名相同。
- 参数列表必须不同,可以是参数类型不同、参数个数不同、参数顺序不同。
- 返回类型不同不能用作函数重载的区分标准。
下面是函数重载的示例:
#include <iostream>
// 函数重载示例
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
int main() {
std::cout << "Sum of integers: " << add(2, 3) << std::endl;
std::cout << "Sum of doubles: " << add(2.5, 3.5) << std::endl;
std::cout << "Sum of three integers: " << add(1, 2, 3) << std::endl;
return 0;
}
在上面的示例中,我们定义了三个具有相同函数名 add
的函数,但是它们的参数列表分别是 (int, int)
、(double, double)
和 (int, int, int)
。通过不同的参数列表,C++编译器能够根据调用的参数类型自动选择正确的函数进行调用。
函数重载可以使代码更具有可读性和简洁性,同时提供了更多的灵活性,以适应不同的情况和参数类型。
4.5 特殊用途语言特性
C++默认实参(Default Arguments)
C++允许你为函数的参数提供默认值,这样在调用函数时,如果没有为这些参数提供值,将使用默认值。默认实参使函数的调用更加方便,并且可以在函数声明或定义中指定。
以下是默认实参的示例:
#include <iostream>
// 函数声明时指定默认实参
void printMessage(std::string message = "Hello, World!");
int main() {
printMessage(); // 使用默认实参
printMessage("Welcome to C++"); // 传递自定义实参
return 0;
}
// 函数定义时指定默认实参
void printMessage(std::string message) {
std::cout << message << std::endl;
}
在上述代码中,函数 printMessage
在声明和定义时都指定了默认实参 "Hello, World!"
。在调用函数时,如果没有提供实参,将使用默认值。
内联函数(Inline Functions)
内联函数是一种编译器指令,用于告诉编译器在调用函数时将函数体的内容直接嵌入到调用处,而不是通过函数调用进行跳转。这样可以减少函数调用的开销,提高代码执行效率,但会增加代码体积。
以下是内联函数的示例:
#include <iostream>
// 声明内联函数
inline int multiply(int a, int b) {
return a * b;
}
int main() {
int result = multiply(5, 3);
std::cout << "Result: " << result << std::endl;
return 0;
}
在上面的示例中,通过 inline
关键字声明了内联函数 multiply
,编译器会尝试将函数调用处替换为函数体。
constexpr
函数
constexpr
函数是在编译时求值的函数,用于计算常量表达式。它可以在编译期间执行,用于在编译时计算出结果,而不是在运行时计算。constexpr
函数的返回值必须是常量表达式,参数也必须是常量表达式。
以下是 constexpr
函数的示例:
#include <iostream>
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(5);
std::cout << "Factorial of 5: " << result << std::endl;
return 0;
}
在上述代码中,factorial
函数使用 constexpr
关键字声明,可以在编译时计算阶乘的结果。
使用默认实参、内联函数和 constexpr
函数可以使代码更加灵活、高效,并充分利用编译器的优化能力。
4.6 函数匹配
在C++中,函数匹配是指选择合适的函数来与给定的函数调用匹配。在函数匹配过程中,实参的类型转换是一个重要的概念,它允许C++在函数调用时进行一些隐式的类型转换以匹配最适合的函数。
以下是关于函数匹配中实参类型转换的示例和说明:
#include <iostream>
void printNumber(int num) {
std::cout << "Integer: " << num << std::endl;
}
void printNumber(double num) {
std::cout << "Double: " << num << std::endl;
}
int main() {
int integerNum = 5;
double doubleNum = 3.14;
printNumber(integerNum); // 调用 void printNumber(int num)
printNumber(doubleNum); // 调用 void printNumber(double num)
return 0;
}
在上述示例中,我们定义了两个具有相同名称 printNumber
的函数,但参数类型不同。当我们调用这些函数时,C++会根据实参的类型进行隐式类型转换,以找到最匹配的函数。在第一个函数调用中,整数实参 integerNum
会被隐式地转换为 int
类型,因此调用了 void printNumber(int num)
。在第二个函数调用中,浮点数实参 doubleNum
会被隐式地转换为 double
类型,因此调用了 void printNumber(double num)
。
C++会进行一些基本的隐式类型转换,如从较窄的整数类型向较宽的整数类型转换,从浮点数类型向整数类型转换等。但需要注意的是,过多的隐式类型转换可能会导致代码不清晰,甚至可能引发一些意想不到的问题。在函数调用时,最好显式地传递正确类型的参数,以确保代码的可读性和可维护性。
另外,C++还支持函数模板,它可以自动进行类型推导和实参类型转换,从而更加灵活地匹配函数。
4.7 函数指针
C++中的函数指针是指向函数的指针变量,可以用于存储和调用函数。函数指针允许你在运行时动态选择要调用的函数,从而提供了更大的灵活性和动态性。
以下是函数指针的基本用法:
#include <iostream>
// 函数原型
int add(int a, int b);
int subtract(int a, int b);
int main() {
// 声明函数指针变量,并初始化为指向 add 函数
int (*funcPtr)(int, int) = add;
int result1 = funcPtr(5, 3); // 调用 add 函数
std::cout << "Result from add: " << result1 << std::endl;
// 将函数指针切换到指向 subtract 函数
funcPtr = subtract;
int result2 = funcPtr(5, 3); // 调用 subtract 函数
std::cout << "Result from subtract: " << result2 << std::endl;
return 0;
}
// 函数定义
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
在上述代码中,我们首先声明了两个函数 add
和 subtract
,然后声明了一个函数指针 funcPtr
,并初始化它为指向 add
函数。通过函数指针调用函数时,需要在指针变量后加上参数列表来传递实参。然后,我们将函数指针切换到指向 subtract
函数,再次使用函数指针调用不同的函数。
函数指针在编写回调函数、实现函数表和动态选择函数等场景中非常有用。它允许程序在运行时决定调用哪个函数,从而实现更加灵活和可扩展的代码结构。