C++快速回顾(一)

前言

在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语句。以下是这些选择语句的示例:

  1. if语句:用于在条件为真时执行一段代码。
#include <iostream>

int main() {
    int num;
    std::cout << "请输入一个整数: ";
    std::cin >> num;

    if (num > 0) {
        std::cout << "您输入的数是正数。" << std::endl;
    }

    return 0;
}
  1. 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;
}
  1. 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循环。以下是这些循环语句的示例:

  1. for循环:用于指定一个初始值、一个循环条件和一个迭代步骤,然后在满足循环条件时重复执行代码块。
#include <iostream>

int main() {
    for (int i = 1; i <= 5; ++i) {
        std::cout << "循环迭代 " << i << std::endl;
    }

    return 0;
}
  1. while循环:在满足指定条件时重复执行代码块。
#include <iostream>

int main() {
    int count = 1;

    while (count <= 5) {
        std::cout << "循环迭代 " << count << std::endl;
        ++count;
    }

    return 0;
}
  1. 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的类,它有两个成员变量 nameage,以及一个成员函数 introduce() 用于打印个人信息。在main()函数中,创建了一个Person对象person1,设置其成员变量的值,然后调用introduce()方法展示个人信息。

这只是一个简单的类示例,C++类的功能远远不止于此。可以在类中添加更多成员变量和成员函数,甚至可以使用访问控制符(publicprivateprotected)来限制成员的访问权限。通过使用类,可以更好地组织代码、封装数据,使代码更加模块化和易于维护(这些内容后面会详细介绍)。


1.5.2 面向过程编程VS面向对象编程

面向过程编程

面向过程编程是一种编程范式,它将程序设计看作是一系列按照特定顺序执行的操作步骤的集合,强调解决问题时需要对问题进行分解,然后逐步地定义操作步骤以达到最终目标。这种编程风格关注的是“做什么”以及“怎么做”,注重操作和流程的控制。

想象一下你正在制作一份烹饪食谱。面向过程编程类似于你将每个烹饪步骤详细列出,从准备材料、切菜、炒菜,到最后的上菜。你会按照特定的顺序,依次执行每个步骤,最终得到一道美味的菜肴。在面向过程编程中,你会将问题分解为一系列可执行的步骤,每个步骤执行特定的操作。

面向过程编程的主要特点包括:

  1. 过程和流程控制: 程序由一系列的过程(函数或方法)组成,每个过程执行特定的任务。程序流程由顺序结构、条件语句和循环等控制。
  2. 重用性较低: 在不同的程序中,可能需要重复编写相似的过程,导致代码重复。
  3. 可读性强: 由于操作步骤清晰可见,理解起来相对容易。
  4. 适用于简单任务: 面向过程编程适合处理一些相对简单的任务,如数值计算、文件处理等。

虽然面向过程编程在某些情况下非常有效,但在处理大型、复杂的软件系统时,面向过程的代码可能会变得难以维护和扩展。这就引入了面向对象编程(OOP)等其他编程范式,以更好地处理复杂性和提高代码的可维护性。

总之,面向过程编程是一种将问题分解为一系列操作步骤的编程方式,适合处理相对简单的任务,但可能在复杂情况下显得不够灵活。


面向对象编程

面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序设计看作是一系列相互协作的对象,强调将数据和操作组合在一起,以模拟真实世界中的事物和关系。这种编程风格注重将问题分解为对象,每个对象代表了一个实体,具有数据和可执行的方法。

上面这段话是很专业吧, 不理解的话我举两个生活中的例子来解释。

  • 案例1

用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。

蛋炒饭制作步骤:准备胡萝卜丁和腊肉丁,准备米饭,起锅烧油,炒鸡蛋,加入米饭胡萝卜丁腊肉丁一起炒,添加调料,炒好之后装盘。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。

蛋炒饭的好处就是入味均匀,吃起来香。如果你不爱吃胡萝卜丁,只爱吃腊肉丁的话,那么就需要全部倒掉,重新做一份腊肉炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。

盖浇饭的好处就是"菜"“饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,“饭” 和"菜"的耦合度比较低。蛋炒饭将"蛋”“饭"搅和在一起,想换"蛋”"饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。

  • 案例2

假设你正在制作一个电子商务网站,我们可以使用类比来说明面向对象编程的概念:

  1. 类和对象
    想象你正在建立一个“商品”(Product)类,这个类描述了商品的特性,比如名称、价格和描述等。每个商品都是这个类的一个实例,就像你在网站上看到的每个商品都是具体的物品。
  2. 封装
    在“商品”类中,你可以将商品的具体细节封装起来,比如将价格设为私有成员变量,这样外部的代码就不能直接访问和修改价格。你可以提供公共的方法(比如“getPrice()”)来访问价格,从而控制对内部数据的访问。
  3. 继承
    假设你还有不同类型的商品,如“书籍”(Book)和“电子产品”(Electronics)。你可以从“商品”类派生出这些子类,每个子类可以继承“商品”类的通用属性(如名称和描述),并且还可以有自己特有的属性(如作者对于“书籍”类)。这样,你可以更好地组织和管理不同类型的商品。
  4. 多态
    在网站上,你可能有一个“购物车”(ShoppingCart)类,它可以存储用户选购的商品。你可以编写一个函数,接受不同类型的商品对象作为参数,然后将它们添加到购物车中。因为这些商品都是“商品”类的子类,多态性使得你可以轻松地处理不同类型的商品,而不必关心具体是哪个子类。
  5. 抽象类和纯虚函数
    假设你想要实现一个优惠券系统,其中某些商品可以应用优惠券,而另一些则不行。你可以创建一个名为“可打折商品”(DiscountableProduct)的抽象类,其中定义一个纯虚函数“applyDiscount()”。然后,“书籍”和“电子产品”等子类可以继承这个抽象类并实现自己的“applyDiscount()”函数,以决定如何应用折扣。

面向对象编程的主要特点包括:

  1. 对象: 程序由一组对象组成,每个对象都是一个实体,具有属性(数据)和方法(操作)。
  2. 封装: 将数据和操作封装在对象内部,隐藏了内部细节,只暴露必要的接口。这有助于降低代码的耦合度,增强了代码的模块化和可维护性。
  3. 继承: 允许一个对象(子类)继承另一个对象(父类)的属性和方法,促进了代码的重用和层次结构。
  4. 多态: 不同的对象可以对相同的消息做出不同的响应,通过多态性可以实现灵活且可扩展的设计。
  5. 抽象: 通过抽象类和接口定义通用的特征和行为,可以让对象具备共同的属性和方法。

面向对象编程适用于处理复杂的软件系统,能够更好地管理和组织代码,使得代码更加可维护、可扩展,并且能够更好地模拟和反映现实世界中的关系。虽然面向对象编程可能需要更多的抽象和设计工作,但它为构建大型、复杂的应用程序提供了强大的工具。


2 变量和基本类型

2.1 基本内置类型

C++中的基本内置数据类型用于存储不同种类的数据。这些基本类型提供了不同的数据大小和精度,以满足各种编程需求。以下是C++中常见的基本内置数据类型:

  1. 整数类型:
    • int:整数类型,通常占用4个字节。
    • short:短整数类型,通常占用2个字节。
    • long:长整数类型,通常占用4或8个字节,取决于系统平台。
    • long long:更长的整数类型,通常占用8个字节。
  2. 无符号整数类型:
    • unsigned int:无符号整数类型,用于表示非负整数。
    • unsigned short:无符号短整数类型。
    • unsigned long:无符号长整数类型。
    • unsigned long long:无符号更长的整数类型。
  3. 浮点数类型:
    • float:单精度浮点数,通常占用4个字节。
    • double:双精度浮点数,通常占用8个字节。
    • long double:更长的双精度浮点数,字节可能更多,取决于系统平台。
  4. 字符类型:
    • char:字符类型,通常占用1个字节,用于存储单个字符。
    • signed char:有符号字符类型。
    • unsigned char:无符号字符类型。
  5. 布尔类型:
    • bool:布尔类型,用于表示真或假(true或false)值。
  6. 空类型:
    • void:空类型,通常用于函数返回值为空或指针类型时。

此外,C++还提供了一些扩展的整数类型,如int8_tuint16_t等,它们有固定的大小,不受系统平台影响,位于头文件<cstdint>中。

类型转换

在C++中,类型转换是将一个数据类型的值转换为另一个数据类型的过程。C++提供了多种类型转换方式,包括隐式类型转换和显式类型转换。以下是常见的类型转换方式:

  1. 隐式类型转换(自动类型转换): 在一些情况下,C++会自动进行类型转换,以满足运算要求或函数参数匹配。例如,整数和浮点数之间的算术运算会导致隐式类型转换。这种转换由编译器自动完成,无需程序员干预。
int num = 5;
double result = num + 2.5; // 隐式将整数转换为双精度浮点数
  1. 显式类型转换(强制类型转换): 在某些情况下,可能需要显式地告诉编译器进行类型转换。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++中常见的字面值常量类型:

  1. 整数字面值: 表示整数的值,可以是十进制、八进制、十六进制等形式。
    • 十进制:42
    • 八进制:052(以0开头)
    • 十六进制:0x2A0X2A(以0x或0X开头)
  2. 浮点数字面值: 表示浮点数的值,可以是小数形式或指数形式。
    • 小数形式:3.14
    • 指数形式:6.022e23(表示6.022乘以10的23次方)
  3. 字符字面值: 表示一个字符,使用单引号括起来。
    • 字符:'A'
    • 转义字符:'\n'(表示换行符)、'\t'(表示制表符)等
  4. 字符串字面值: 表示一个字符串,使用双引号括起来。
    • 字符串:"Hello, world!"
  5. 布尔字面值: 表示布尔值,只有两个值:truefalse
    • 布尔值:truefalse
  6. 空指针字面值: 表示空指针,使用关键字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;
}

在上面的示例中,我们首先声明了不同类型的变量(ageweightinitialname),然后赋值给这些变量,最后在控制台输出了变量的值。

值得注意的是,变量名必须遵循一定的规则:

  • 变量名由字母、数字和下划线组成,且不能以数字开头。
  • 变量名区分大小写,例如,myVariablemyvariable被视为不同的变量。
  • 变量名不能与C++的关键字(例如intdoubleif等)相同。

在C++中,变量的作用域指的是变量在代码中可见的范围。变量可以具有局部作用域(在特定代码块内可见)或全局作用域(在整个程序中可见)。


2.3 复合类型

在C++中,复合类型是由基本数据类型组合而成的数据类型,可以在一个变量中存储多个值或数据。C++提供了几种常见的复合类型,包括数组、指针、引用和结构体(struct)。

以下是这些复合类型的简要介绍:

  1. 数组(Array): 数组是一组相同类型的元素的集合,通过索引访问各个元素。数组可以在声明时指定大小,也可以使用动态分配的方式创建。
int numbers[5]; // 声明一个包含5个整数的数组
double temperatures[10]; // 声明一个包含10个双精度浮点数的数组

// 初始化数组
int primes[] = {2, 3, 5, 7, 11};
  1. 指针(Pointer): 指针是一个变量,用于存储另一个变量的内存地址。指针允许你直接访问变量所在的内存位置。
int num = 42;
int* ptr; // 声明一个整数指针
ptr = &num; // 指针指向num的地址

// 访问指针指向的值
int value = *ptr; // value将等于42
  1. 引用(Reference): 引用是变量的别名,允许你通过不同的名称访问同一个变量。引用通常用于函数参数传递和函数返回值。
int num = 10;
int& ref = num; // 声明一个整数引用,它是num的别名

ref = 20; // 修改ref,也会修改num的值
  1. 结构体(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 在不同上下文中的用法:

  1. 常量变量: 使用 const 修饰变量,表示该变量的值在初始化后不可修改。
const int age = 25;
const double pi = 3.14159;
  1. 常量指针: 使用 const 修饰指针,表示指针指向的值不可修改。
int num = 42;
const int* ptr = &num; // ptr 是指向常量的指针,不可通过 ptr 修改 num 的值

int x = 10;
int y = 20;
int* const p = &x; // p 是常量指针,指向 x,但 p 的值不可修改
  1. 常量引用: 使用 const 修饰引用,表示通过引用不可修改原始变量的值。
int num = 10;
const int& ref = num; // ref 是常量引用,不可通过 ref 修改 num 的值
  1. 函数参数: 使用 const 修饰函数参数,表示函数内部不会修改参数的值。
void printValue(const int value) {
    // value 是常量,不可修改
    std::cout << "Value: " << value << std::endl;
}
  1. 函数返回值: 使用 const 修饰函数返回值,表示返回的值不可修改。
const int getNumber() {
    return 42;
}

const 限定符可以增加代码的清晰度,防止无意的数据修改,以及在函数中传递数据时确保数据的安全性。使用 const 有助于编写更健壮、可维护的代码。


2.5 处理类型

在C++中,有几种用于处理类型的工具,包括类型别名、auto 类型说明符和 decltype 类型指示符。这些工具有助于简化代码、提高可读性和灵活性。

  1. 类型别名(Type Alias): 类型别名允许你为一个已有类型赋予一个新的名字,使代码更加清晰和可读。可以使用关键字 usingtypedef 来创建类型别名。
using Distance = double; // 创建一个类型别名 Distance,表示距离(双精度浮点数)
typedef int ItemCount;   // 创建一个类型别名 ItemCount,表示物品数量(整数)
  1. auto 类型说明符: auto 关键字允许编译器自动推断变量的类型,根据初始化表达式的类型来确定变量的类型。这在简化代码和处理复杂类型时特别有用。
auto x = 42;       // x 的类型将被推断为 int
auto name = "John"; // name 的类型将被推断为 const char*
  1. 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 对象的几种方式:

  1. 默认构造函数: 创建一个空的 string 对象。
#include <string>

std::string str; // 默认构造函数创建一个空字符串
  1. 使用字符串字面值初始化:
std::string greeting = "Hello, world!";
  1. 拷贝构造函数: 从另一个 string 对象复制内容。
std::string original = "Hello";
std::string copy = original; // 使用拷贝构造函数复制内容
  1. 使用 = 运算符:
std::string name;
name = "Alice"; // 使用 = 运算符赋值
  1. 使用 + 运算符连接字符串:
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 对象的几种方式:

  1. 默认构造函数: 创建一个空的 vector 对象。
#include <vector>

std::vector<int> numbers; // 默认构造函数创建一个空的整数向量
  1. 指定大小并初始化:
std::vector<double> prices(10, 0.0); // 创建一个包含10个双精度浮点数的向量,并初始化为0.0
  1. 使用初始化列表:
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::vectorstd::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)是指在同一个作用域内,可以定义具有相同名称但不同参数列表的多个函数。函数重载允许你根据不同的参数类型和数量来调用不同的函数,提供了更灵活的函数调用方式。

函数重载的规则如下:

  1. 函数名相同。
  2. 参数列表必须不同,可以是参数类型不同、参数个数不同、参数顺序不同。
  3. 返回类型不同不能用作函数重载的区分标准。

下面是函数重载的示例:

#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;
}

在上述代码中,我们首先声明了两个函数 addsubtract,然后声明了一个函数指针 funcPtr,并初始化它为指向 add 函数。通过函数指针调用函数时,需要在指针变量后加上参数列表来传递实参。然后,我们将函数指针切换到指向 subtract 函数,再次使用函数指针调用不同的函数。

函数指针在编写回调函数、实现函数表和动态选择函数等场景中非常有用。它允许程序在运行时决定调用哪个函数,从而实现更加灵活和可扩展的代码结构。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值