C++基础

1,099 阅读56分钟

C++ 进一步扩充和完善了 C 语言,像Java一样它也是一种面向对象的程序设计语言。

上一篇:C语言基础

1. 程序结构

让我们逐帧分析

#include <iostream>
using namespace std;
 
// main() 是程序开始执行的地方
 
int main()
{
   cout << "Hello World"; // 输出 Hello World
   //    cout << "Hello, world!" << endl;
   return 0;
}

#include <iostream> :包含了输入输出流库 iostream
using namespace std :告诉编译器使用标准命名空间( std)
cout << "Hello World"; :

  • cout: 是 C++ 中的标准输出流,用于向控制台输出信息。
  • <<: 是流插入运算符,用于将右侧的内容输出到左侧的流中。
  • "Hello World": 是要输出的字符串。它将在控制台上显示。

return 0; :终止 main( )函数,并向调用进程返回值 0
cout << "Hello, world!" << endl 输出 Hello, world! 的另一种写法,

  • << endl: endl 是一个特殊的操作符,它会在输出后插入一个换行符并刷新输出缓冲区。

2. 数据类型

类型关键字
布尔型bool
字符型char
整型int
浮点型float
双浮点型double
无类型void
宽字符型wchar_t

详细介绍:

类型描述大小值范围
整数类型
int标准整数类型通常 4 字节-2,147,483,648 到 2,147,483,647
short短整型通常 2 字节-32,768 到 32,767
long长整型4 或 8 字节32 位系统与 int 相同,64 位系统可更大
long long更长整型至少 8 字节-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
浮点类型
float单精度浮点型通常 4 字节±3.4 × 10^−38 到 ±3.4 × 10^38
double双精度浮点型通常 8 字节±1.7 × 10^−308 到 ±1.7 × 10^308
long double长双精度浮点型8 字节或更多提供更大范围和更高精度
字符类型
char字符型通常 1 字节-128 到 127 或 0 到 255
wchar\_t宽字符型通常 2 或 4 字节用于存储 Unicode 字符
布尔类型
bool布尔型通常 1 字节只有两个值:true 和 false

更多类型:

数据类型定义示例代码
枚举类型 (enum)用于定义一组命名的整型常量,提高代码可读性。enum Color { RED, GREEN, BLUE };
结构类型 (struct)用户定义的数据类型,组合不同类型的数据。struct Person { string name; int age; };
类类型 (class)C++ 的核心特性,支持封装、继承和多态。class Car { public: string brand; int year; };
联合类型 (union)允许在同一内存位置存储不同类型的数据,只有一个成员占用存储空间。union Data { int intValue; float floatValue; char charValue; };
其他数据类型void: 表示没有类型,常用于函数返回类型。void function() { ... }
指针类型: 用于存储变量的地址,以 * 前缀表示。int\* ptr;
引用类型: 创建变量的别名,以 & 表示。int& ref = originalVar;
类型修饰符改变基本数据类型的属性。
signed: 带符号类型(默认)。signed int a;
unsigned: 无符号类型,扩展可表示的正整数范围。unsigned int b;
short / long: 改变整型的大小。结合使用,例如 unsigned long。unsigned long c;

注意: 不同系统会有所差异,一字节为 8 位。
注意: 默认情况下,int、short、long都是带符号的,即 signed。
注意: long int 8 个字节,int 都是 4 个字节,早期的 C 编译器定义了 long int 占用 4 个字节,int 占用 2 个字节,新版的 C/C++ 标准兼容了早期的这一设定。

在此不做进一步详细介绍,相比于菜鸟教程,那上面还有好多类型,而且C++还有很多标准,头大,还好C++不是我的主语言,这篇文章主打一个了解,够用!

2.2 typedef 声明(这个很常用)

使用 typedef 为一个已有的类型取一个新的名字
基本语法

typedef existing_type new_type_name;

示例:

typedef int Integer;
Integer a = 5;  // 这里Integer是int的别名

typedef int* IntPtr;
IntPtr p1, p2;  // p1和p2都是int指针

typedef int Array[10];
Array arr;  // arr是一个包含10个整数的数组

struct Person {
    std::string name;
    int age;
};
typedef Person PersonType;
PersonType p;  // p是Person结构体的一个实例

typedef void (*FuncPtr)(int, double);
void myFunction(int, double) { /* 函数实现 */ }
FuncPtr ptr = myFunction;  // ptr是指向myFunction的函数指针

2.3 类型转换

C++提供了四种主要的类型转换:静态转换( static_cast)、动态转换( dynamic_cast)、常量转换( const_cast)和重新解释转换( reinterpret_cast)

2.3.1 静态转换 ( static_cast)
用于大多数基本类型之间的转换,如整型到浮点型;也可以用于类之间的转换(基类与派生类)。

int i = 10;
float f = static_cast<float>(i); // 静态将int类型转换为float类型

2.3.2 动态转换 ( dynamic_cast)
主要用于处理多态,安全地将基类指针或引用转换为派生类指针或引用。

class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针

2.3.3 常量转换 ( const_cast)
用于去掉对象的常量性,允许修改一个常量对象。

const int a = 10;
int* b = const_cast<int*>(&a);  // 去掉常量性
*b = 20;  // 修改常量对象的值,可能导致未定义行为

2.3.4 重新解释转换 ( reinterpret_cast)
用于在不考虑数据类型的情况下转换指针或引用的类型。

int* p = reinterpret_cast<int*>(0x12345678);  // 将地址转为 int 指针
char* c = reinterpret_cast<char*>(p);          // 将 int 指针转为 char 指针

3. 变量类型

在C++中,变量是用来存储数据的命名内存位置。

3.1 变量定义

变量定义不仅声明了变量的名字和类型,而且为其分配内存并可选地初始化变量。\

cpp-variable-types-2020-12-14.png

3.2 变量声明

变量声明是告诉编译器某个变量的名字和类型,但并不分配内存或初始化变量。声明的目的是让编译器知道该变量将要使用,以便在后续代码中进行类型检查。

extern int x;  // 声明变量 x,类型为 int

变量声明及定义示例

#include <iostream>
using namespace std;
 
// 变量声明
extern int a, b;
extern int c;
extern float f;
  
int main ()
{
  // 变量定义
  int a, b;
  int c;
  float f;
 
  // 实际初始化
  a = 10;
  b = 20;
  c = a + b;
 
  cout << c << endl ;
 
  f = 70.0/3.0;
  cout << f << endl ;
 
  return 0;
}

3.3 左值与右值

左值(Lvalue)
左值(Left Value)是指可以出现在赋值运算符左侧的表达式,通常是指可以持久化存在的对象,具有可寻址性。换句话说,左值是一个具有持久存储的对象,它的地址是可以取出的。

int a = 5;  // a 是一个左值
a = 10;     // a 可以在赋值运算符左侧

右值(Rvalue)
右值(Right Value)是指不能出现在赋值运算符左侧的表达式,通常是临时对象或常量。右值一般是不能持久化存在的对象,它的值不能被直接修改。

int b = 5;   // 5 是一个右值
b = a + 3;   // a + 3 是一个右值

同大多数语言一样,没什么好说的,这里只是了解下。


4. 变量作用域

在 C++ 中,变量的作用域(scope)是指变量在程序中的可见性和生存期

4.1 全局作用域

  • 定义:在所有函数外部声明的变量具有全局作用域。
  • 可见性:在整个程序中可见,所有函数都可以访问。
  • 生存期:从程序开始到结束。
int globalVar = 10; // 全局变量

void function() {
    std::cout << globalVar; // 可以访问
}

4.2 局部作用域

  • 定义:在函数或代码块内部声明的变量具有局部作用域。
  • 可见性:仅在其声明的函数或代码块内部可见。
  • 生存期:当控制流离开其作用域时,变量会被销毁。
void function() {
    int localVar = 20; // 局部变量
    std::cout << localVar; // 可以访问
}
// std::cout << localVar; // 错误:无法访问

4.3 块作用域

  • 定义:在大括号 {} 内声明的变量具有块作用域。
  • 可见性:仅在该块内部可见。
  • 生存期:当控制流离开块时,变量被销毁。
void function() {
    if (true) {
        int blockVar = 30; // 块作用域变量
        std::cout << blockVar; // 可以访问
    }
    // std::cout << blockVar; // 错误:无法访问
}

4.4 类作用域

  • 定义:在类内部声明的变量具有类作用域。
  • 可见性:在类的所有成员函数中可见,且在类的实例中可访问(通过对象)。
  • 生存期:与类的实例一起存在。
class MyClass {
public:
    int classVar; // 类成员变量

    void display() {
        std::cout << classVar; // 可以访问
    }
};

4.5 命名空间作用域

  • 定义:在命名空间内部声明的变量具有命名空间作用域。
  • 可见性:在该命名空间内的所有函数可见。
  • 生存期:从程序开始到结束,类似全局变量。
namespace MyNamespace {
    int namespaceVar = 40; // 命名空间变量
}

void function() {
    std::cout << MyNamespace::namespaceVar; // 可以访问
}

4.6 作用域优先级

  1. 局部作用域

  2. 块作用域

  3. 类作用域

  4. 命名空间作用域

  5. 全局作用域

4.7 作用域总结

作用域类型可见性生存期示例
全局作用域在整个程序中可见程序开始到结束int globalVar = 10;
局部作用域仅在函数或代码块内部可见控制流离开作用域时被销毁void function() { int localVar = 20; }
块作用域仅在大括号内可见控制流离开块时被销毁if (true) { int blockVar = 30; }
类作用域在类的所有成员函数中可见与类的实例一起存在class MyClass { int classVar; };
命名空间作用域在命名空间内可见程序开始到结束namespace MyNamespace { int namespaceVar = 40; }

5. 常量

常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。

5.1 整数常量

整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

212         // 合法的
215u        // 合法的
0xFeeL      // 合法的
078         // 非法的:8 不是八进制的数字
032UU       // 非法的:不能重复后缀

以下是各种类型的整数常量的实例:

85         // 十进制
0213       // 八进制 
0x4b       // 十六进制 
30         // 整数 
30u        // 无符号整数 
30l        // 长整数 
30ul       // 无符号长整数

5.2 浮点常量

浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。

3.14159       // 合法的 
314159E-5L    // 合法的 
510E          // 非法的:不完整的指数
210f          // 非法的:没有小数或指数
.e55          // 非法的:缺少整数或分数

5.3 布尔常量

  • true 值代表真。
  • false 值代表假。

我们不应把 true 的值看成 1,把 false 的值看成 0。

5.4 字符常量

字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 char 类型的简单变量中。

转义序列含义
\\ 字符
'' 字符
"" 字符
?? 字符
\a警报铃声
\b退格键
\f换页符
\n换行符
\r回车
\t水平制表符
\v垂直制表符
\ooo一到三位的八进制数
\xhh . . .一个或多个数字的十六进制数

5.5 字符串常量

字符串字面值或常量是括在双引号 "" 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string greeting = "hello, runoob";
    cout << greeting;
    cout << "\n";     // 换行符
    string greeting2 = "hello, \
                        runoob";
    cout << greeting2;
    return 0;
}

5.6 定义常量

在 C++ 中,有两种简单的定义常量的方式:

  • 使用 #define 预处理器。
  • 使用 const 关键字。

5.6.1 #define 预处理器
#define 是一种预处理器指令,通常用于定义宏常量。宏常量在编译时会被预处理器替换为相应的值。

语法

#define CONSTANT_NAME value

示例

#define PI 3.14159
#define MAX_SIZE 100

5.6.2 const 关键字
const 是 C++ 的一个关键字,用于声明常量。在 const 定义的常量中,类型是显式的,并且可以在编译时检查类型。

语法

const type CONSTANT_NAME = value;

示例

const double PI = 3.14159;
const int MAX_SIZE = 100;

5.6.3 #define 与 const 的区别

特性#defineconst
类型无类型有类型
存储不占用内存(文本替换)占用内存
作用域全局块作用域
类型检查无有
使用灵活性灵活(可以定义复杂宏)适合声明常量
调试友好不易调试更易调试(有类型信息)

注意,把常量定义为大写字母形式,是一个很好的编程实践。


6. 修饰符类型

在 C++ 中,修饰符主要用于改变变量、函数、类等的行为和属性。它们可以分为几类:访问控制修饰符、存储类修饰符、类型修饰符和其他修饰符。

修饰符类型修饰符作用/描述
访问控制修饰符public成员可以被任何其他代码访问。
protected成员只能被该类及其派生类访问。
private成员只能被该类内部访问,外部无法访问。
存储类修饰符auto默认存储类别,变量的生命周期为块级,局部变量使用此修饰符时通常可以省略。
register建议编译器将变量存储在寄存器中以提高访问速度,不能取地址。
static变量的生命周期为整个程序运行期间,作用域为定义它的块或文件。
extern声明一个外部变量,指示该变量定义在其他文件中。
mutable允许在 const 成员函数中修改类的成员变量。
类型修饰符const指示变量的值不能被改变,确保数据的只读性。
volatile告诉编译器该变量的值可能在程序之外被改变,避免优化。
signed指定整数类型为有符号类型。
unsigned指定整数类型为无符号类型。
long指定整数或浮点数类型的更大表示范围。
short指定整数类型的较小表示范围。
其他修饰符friend允许其他类或函数访问当前类的私有和保护成员。
inline建议编译器将函数内联,以提高性能。
virtual用于支持多态性,允许派生类重写基类的成员函数。
override用于指示一个虚函数被派生类重写,编译器检查是否匹配。
final用于防止类被继承或函数被重写。
signed int num1 = -10; // 定义有符号整型变量 num1,初始值为 -10
unsigned int num2 = 20; // 定义无符号整型变量 num2,初始值为 20

short int num1 = 10; // 定义短整型变量 num1,初始值为 10
long int num2 = 100000; // 定义长整型变量 num2,初始值为 100000

long long int num1 = 10000000000; // 定义长长整型变量 num1,初始值为 10000000000

float num1 = 3.14f; // 定义单精度浮点数变量 num1,初始值为 3.14
double num2 = 2.71828; // 定义双精度浮点数变量 num2,初始值为 2.71828

bool flag = true; // 定义布尔类型变量 flag,初始值为 true

char ch1 = 'a'; // 定义字符类型变量 ch1,初始值为 'a'
wchar_t ch2 = L'你'; // 定义宽字符类型变量 ch2,初始值为 '你'

6.1 类型限定符

限定符含义
constconst 定义常量,表示该变量的值不能被修改。
volatile修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。。
restrict由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。
mutable表示类中的成员变量可以在 const 成员函数中被修改。
static用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。
register用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。

const 实例

const int NUM = 10; // 定义常量 NUM,其值不可修改
const int* ptr = &NUM; // 定义指向常量的指针,指针所指的值不可修改
int const* ptr2 = &NUM; // 和上面一行等价

volatile 实例

volatile int num = 20; // 定义变量 num,其值可能会在未知的时间被改变

mutable 实例

class Example {
public:
    int get_value() const {
        return value_; // const 关键字表示该成员函数不会修改对象中的数据成员
    }
    void set_value(int value) const {
        value_ = value; // mutable 关键字允许在 const 成员函数中修改成员变量
    }
private:
    mutable int value_;
};

static 实例

void example_function() {
    static int count = 0; // static 关键字使变量 count 存储在程序生命周期内都存在
    count++;
}

register 实例

void example_function(register int num) {
    // register 关键字建议编译器将变量 num 存储在寄存器中
    // 以提高程序执行速度
    // 但是实际上是否会存储在寄存器中由编译器决定
}

7. 存储类

在 C++ 中,存储类(Storage Class)用于指定变量的生命周期、作用域以及存储位置。C++ 提供了以下几种存储类,它们控制变量的作用范围、初始化方式以及是否能跨函数或文件访问。这些存储类分为四种主要类型:自动存储类、静态存储类、外部存储类、和注册存储类。

7.1 自动存储类(auto)

  • 定义:自动变量是最常见的局部变量。它们的生命周期仅限于它们所在的函数或块的执行期间。每次进入函数时,自动变量会被创建并初始化,离开函数时会被销毁。
  • 作用域:局部作用域(函数或代码块内)。
  • 生命周期:从变量创建开始,到函数退出时销毁。
  • 默认情况下:所有局部变量默认为自动变量(如果没有指定其他存储类修饰符)。你可以显式地使用 auto 关键字来声明一个自动变量,但在现代 C++ 中, auto 更常用于自动类型推断。
void func() {
    int x = 10; // 自动变量
} // x 在函数结束时被销毁

7.2 静态存储类(static)

  • 定义:静态变量在程序的生命周期内存在,直到程序结束。它们的作用域通常局限于它们所在的函数或文件,但与自动变量不同的是,静态变量不会在每次调用时重新创建,而是保持上次的值。
  • 作用域:局部作用域(如果定义在函数内部),全局作用域(如果定义在函数外部且没有外部链接)。
  • 生命周期:从程序开始到程序结束。
    特点:
    • 如果在函数内部声明静态变量,那么该变量在程序执行期间只会初始化一次,并且每次调用该函数时,它的值会被保留。
    • 如果在函数外部声明静态变量,则它的作用域仅限于文件,外部无法访问。

示例:


void func() {
    static int count = 0; // 静态变量
    count++;
    std::cout << count << std::endl; // 每次调用时都会输出递增的值
}

int main() {
    func(); // 输出 1
    func(); // 输出 2
    func(); // 输出 3
}

7.3 外部存储类(extern)

  • 定义: extern 关键字用于声明一个变量在其他文件中定义,即该变量是一个外部变量,或者说在其他地方已经声明过,并且我们在当前文件中引用它。
  • 作用域:全局作用域(可以跨多个文件访问)。
  • 生命周期:程序的整个生命周期。
    用途:
    • extern 用于让一个变量在多个文件之间共享。声明外部变量时,它的内存分配发生在其他文件中。

示例:

// file1.cpp
int x = 10; // 在文件1中定义变量

// file2.cpp
extern int x; // 在文件2中声明变量
std::cout << x << std::endl; // 访问 file1.cpp 中定义的变量

7.4 注册存储类(register)

  • 定义: register 存储类建议编译器将变量存储在寄存器中而不是内存中,以提高访问速度。寄存器是 CPU 中的高效存储器,访问速度非常快。
  • 作用域:局部作用域。
  • 生命周期:与自动变量相同,从函数调用开始到结束。
    特点:
    • 由于寄存器空间有限,编译器可能会忽略 register 关键字,特别是在变量的存储需求较大时。
    • 不能使用 & 操作符获取寄存器变量的地址,因为寄存器变量可能没有固定的内存地址。

示例:


void func() {
    register int count = 0; // 尝试将 count 存储在寄存器中
    count++;
}

7.5 线程存储类(thread_local)

  • 定义: thread\_local 存储类修饰符用于声明线程局部变量。每个线程会有一个独立的实例,而不是共享同一个变量的实例。它使变量在每个线程中都有一个独立的副本。
  • 作用域:线程作用域。
  • 生命周期:线程生命周期内。

示例:

thread_local int count = 0;

void func() {
    count++; // 每个线程有独立的 count
}

7.6 存储类修饰符总结

存储类作用作用域生命周期是否可修改
auto默认局部变量局部变量函数执行期间可以修改
static静态局部变量局部变量程序的整个生命周期可以修改
extern外部变量全局变量程序的整个生命周期可以修改
register寄存器变量局部变量函数执行期间可以修改
thread\_local线程局部变量局部变量线程生命周期可以修改

从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。

7.7 存储类使用场景

  • 自动存储类(auto):通常用在局部变量上,适用于不需要跨函数调用保留的变量。
  • 静态存储类(static):在需要跨函数调用保留变量的情况下使用,如计数器、缓存等。
  • 外部存储类(extern):用于跨多个文件共享变量时,如模块间共享的全局变量。
  • 寄存器存储类(register):当变量访问非常频繁且需要提高性能时,可以使用 register(尽管现代编译器优化可以自动处理)。
  • 线程存储类(thread_local):当每个线程需要独立存储一些数据时使用,如线程局部存储(TLS)变量。

8. 运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C++ 内置了丰富的运算符,并提供了以下类型的运算符:

  • 算术运算符

  • 关系运算符

  • 逻辑运算符

  • 位运算符

  • 赋值运算符

  • 杂项运算符

8.1 算术运算符

运算符描述实例
+把两个操作数相加A + B 将得到 30
-从第一个操作数中减去第二个操作数A - B 将得到 -10
*把两个操作数相乘A * B 将得到 200
/分子除以分母B / A 将得到 2
%取模运算符,整除后的余数B % A 将得到 0
++自增运算符,整数值增加 1A++ 将得到 11
--自减运算符,整数值减少 1A— 将得到 9

8.2 关系运算符

运算符描述实例
==检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 不为真。
!=检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 不为真。
<检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 不为真。
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。

8.3 逻辑运算符

运算符描述实例
&&称为逻辑与运算符。如果两个操作数都 true,则条件为 true。(A && B) 为 false。
||称为逻辑或运算符。如果两个操作数中有任意一个 true,则条件为 true。(A || B) 为 true。
!称为逻辑非运算符。用来逆转操作数的逻辑状态,如果条件为 true 则逻辑非运算符将使其为 false。!(A && B) 为 true。

8.4 位运算符

pqp & qp |qp ^ q
00000
01011
11110
10011

下表显示了 C++ 支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:

运算符描述实例
&按位与操作,按二进制位进行"与"运算。运算规则:<br>0&0=0; <br>0&1=0; <br>1&0=0; <br>1&1=1;<br>(A & B) 将得到 12,即为 0000 1100
|按位或运算符,按二进制位进行"或"运算。运算规则: 0|0=0; 0|1=1; 1|0=1; 1|1=1;(A | B) 将得到 61,即为 0011 1101
异或运算符,按二进制位进行"异或"运算。运算规则: 0^0=0; 0^1=1; 1^0=1; 1^1=0;(A ^ B) 将得到 49,即为 0011 0001
~取反运算符,按二进制位进行"取反"运算。运算规则: ~1=-2; ~0=-1;(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
<<二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。A << 2 将得到 240,即为 1111 0000
>>二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。A >> 2 将得到 15,即为 0000 1111

8.5 赋值运算符

运算符描述实例
=简单的赋值运算符,把右边操作数的值赋给左边操作数C = A + B 将把 A + B 的值赋给 C
+=加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数C += A 相当于 C = C + A
-=减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数C -= A 相当于 C = C - A
*=乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数C *= A 相当于 C = C * A
/=除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数C /= A 相当于 C = C / A
%=求模且赋值运算符,求两个操作数的模赋值给左边操作数C %= A 相当于 C = C % A
<<=左移且赋值运算符C <<= 2 等同于 C = C << 2
=右移且赋值运算符C >>= 2 等同于 C = C >> 2
&=按位与且赋值运算符C &= 2 等同于 C = C & 2
^=按位异或且赋值运算符C ^= 2 等同于 C = C ^ 2
|==按位或且赋值运算符

8.6 杂项运算符

运算符描述
sizeofsizeof 运算符 返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。
Condition ? X : Y条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。
,逗号运算符 会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。
.(点)和 ->(箭头)成员运算符 用于引用类、结构和共用体的成员。
Cast强制转换运算符 把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。
&指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。
*指针运算符 * 指向一个变量。例如,* *var; 将指向变量 var。

8.7 运算符优先级

不用记,这东西就像乘法口诀一样,上手就会。

类别运算符结合性
后缀() [] -> . ++ - -从左到右
一元! ~ ++ - - (type)* & sizeof从右到左
乘除/ %从左到右
加减+ -从左到右
移位<< >>从左到右
关系< <= > >=从左到右
相等== !=从左到右
位与 AND&从左到右
位异或 XOR从左到右
位或 OR|从左到右
逻辑与 AND&&从左到右
逻辑或 OR||从左到右
条件?:从右到左
赋值= += -= *= /= %=>>= <<= &= ^==
逗号,从左到右

9. 循环

同Java语言一样,没什么特别之处

9.1 循环类型

循环类型描述
while 循环当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for 循环多次执行一个语句序列,简化管理循环变量的代码。
do...while 循环除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
嵌套循环您可以在 while、for 或 do..while 循环内使用一个或多个循环。

9.2 循环控制语句

控制语句描述
break 语句终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。
continue 语句引起循环跳过主体的剩余部分,立即重新开始测试条件。
goto 语句将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

9.3 无限循环

#include <iostream>
using namespace std;
 
int main ()
{
 
   for( ; ; )
   {
      printf("This loop will run forever.\n");
   }
 
   return 0;
}

10. 判断

在C++中,判断通常是通过条件语句来实现的,主要用于控制程序的执行流程。

10.1 判断语句

语句描述
if 语句一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
if...else 语句一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
嵌套 if 语句您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。
switch 语句一个 switch 语句允许测试一个变量等于多个值时的情况。
嵌套 switch 语句您可以在一个 switch 语句内使用另一个 switch 语句。

10.2 ? : 运算符

前面说了 条件运算符 ? :,可以用来替代 if...else 语句。它的一般形式如下:

Exp1 ? Exp2 : Exp3;

11. 函数

函数是一组一起执行一个任务的语句。每个 C++ 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。

11.1 定义函数

同Java一样

return_type function_name( parameter list )
{
   body of the function
}

11.2 函数声明(重点)

函数声明是告诉编译器函数的名称、返回类型和参数类型,但不包含函数体。这通常在函数使用之前进行,以便编译器能够知道该函数的存在。
语法:

return_type function_name( parameter list );

示例:

int add(int, int); // 函数声明

11.3 调用函数

函数调用是执行已定义函数的过程。可以直接使用函数名和参数来调用函数。

int add(int, int); // 函数声明

int main() {
    int sum = add(5, 10); // 调用函数
    return 0;
}

int add(int a, int b = 10) { // b 的默认值为 10
    return a + b;
}

注意:在C++中,函数可以在一个文件中声明,并在其他文件中调用。

11.3 函数参数(重点)

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有三种向函数传递参数的方式:

调用类型描述
传值调用该方法把参数的实际值赋值给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
指针调用该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
引用调用该方法把参数的引用赋值给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
特性传值调用指针调用引用调用
传递方式值的副本地址引用
对实参的影响无影响直接影响直接影响
语法简洁性简单需要解引用操作简单
内存使用使用更多(复制对象)使用较少(传递地址)使用较少(传递地址)
安全性高可能为空指针(需检查)高
性能较低(复制开销)较高较高

11.4 参数的默认值

在 C++ 中,可以为函数参数提供默认值,这样在调用函数时可以省略某些参数。
示例:

int add(int a, int b = 10) { // b 的默认值为 10
    return a + b;
}

int main() {
    int sum1 = add(5);    // sum1 = 15, b 使用默认值
    int sum2 = add(5, 20); // sum2 = 25, b 被显式传递
}

good,good ,这点相较于Java比较好,新秀语言一般都支持这一特性。

11.5 Lambda 函数与表达式

Lambda 函数是 C++11 引入的一种匿名函数,可以在定义时立即使用。它们通常用于简化代码,比如在算法中使用时。
语法:

[捕获列表](参数列表) -> 返回类型 {
    // 函数体
}

示例:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Lambda 函数,用于计算平方
    auto square = [](int x) -> int {
        return x * x;
    };

    for (const auto& num : numbers) {
        std::cout << square(num) << " "; // 输出 1 4 9 16 25
    }

    // 使用 Lambda 函数进行排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b; // 降序排序
    });

    return 0;
}

emm.. 什么奇葩结构,一眼看不懂系列,感兴趣的可以单独去研究,这里一笔带过。


12. 数字

12.1 定义数字

#include <iostream>
using namespace std;
 
int main ()
{
   // 数字定义
   short  s;
   int    i;
   long   l;
   float  f;
   double d;
   
   // 数字赋值
   s = 10;      
   i = 1000;    
   l = 1000000; 
   f = 230.47;  
   d = 30949.374;
   
   // 数字输出
   cout << "short  s :" << s << endl;
   cout << "int    i :" << i << endl;
   cout << "long   l :" << l << endl;
   cout << "float  f :" << f << endl;
   cout << "double d :" << d << endl;
 
   return 0;
}

12.2 数学运算

在 C++ 中,除了可以创建各种函数,还包含了各种有用的函数供您使用。这些函数写在标准 C 和 C++ 库中,叫做内置函数。您可以在程序中引用这些函数。
这些函数包含在 cmath 中

序号函数 & 描述
1double cos(double); 该函数返回弧度角(double 型)的余弦。
2double sin(double); 该函数返回弧度角(double 型)的正弦。
3double tan(double); 该函数返回弧度角(double 型)的正切。
4double log(double); 该函数返回参数的自然对数。
5**double pow(double, double);**假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方。
6double hypot(double, double); 该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。
7double sqrt(double); 该函数返回参数的平方根。
8int abs(int); 该函数返回整数的绝对值。
9double fabs(double); 该函数返回任意一个浮点数的绝对值。
10double floor(double); 该函数返回一个小于或等于传入参数的最大整数。

12.3 随机数

在 C++ 中,生成随机数可以使用 <cstdlib> 头文件中的 rand() 函数。为了生成更好的随机数,可以结合使用 srand() 函数来设置种子。

示例代码:

#include <iostream>
#include <cstdlib>
#include <ctime>

int main() {
    std::srand(static_cast<unsigned int>(std::time(0))); // 设置随机种子
    for (int i = 0; i < 5; ++i) {
        int randomNum = std::rand() % 100; // 生成 0 到 99 的随机数
        std::cout << "Random Number: " << randomNum << std::endl;
    }
    return 0;
}

关于随机种子:
每次运行这个程序,生成的随机数都会不同,因为每次调用 std::time(0) 获取的时间都是不同的,从而使得种子不同。这种方式使得随机数生成更加“随机”,避免每次运行程序时得到相同的随机数序列。


13. 数组

C++ 中的数组是一种用于存储多个相同类型数据的容器。数组可以有效地组织和管理数据,提供了一种便捷的方式来处理一组相关的元素。

13.1 数组的定义

type arrayName[arraySize];
  • type:数组中元素的类型(如 int、 float、 char 等)。
  • arrayName:数组的名称。
  • arraySize:数组中元素的个数,必须是正整数。

示例

int numbers[5];  // 定义一个可以存储 5 个整数的数组

13.2 数组的特性

  • 静态存储:数组的大小在定义后是固定的,不能改变。
  • 连续存储:数组中的元素在内存中是连续存放的,这使得元素的访问速度较快。
  • 类型一致性:数组中的所有元素必须是同一类型。
  • 下标访问:可以通过下标直接访问数组的元素,下标从 0 开始。

13.3 数组的初始化

逐个初始化

逐个指定每个元素的值:

int numbers[5] = {1, 2, 3, 4, 5};  // 逐个初始化

部分初始化

未初始化的元素将自动被设置为 0:

int numbers[5] = {1, 2};  // numbers[2] = 0, numbers[3] = 0, numbers[4] = 0

不指定大小

在初始化时可以不指定数组的大小:

int numbers[] = {1, 2, 3, 4, 5};  // 编译器推断数组大小为 5

13.4 数组的操作

访问和修改元素

numbers[0] = 10;  // 修改第一个元素为 10
int first = numbers[0];  // 读取第一个元素

遍历数组

for (int i = 0; i < 5; ++i) {
    std::cout << numbers[i] << " ";  // 打印数组的每个元素
}

13.5 多维数组

C++ 支持多维数组,通常用于表示矩阵或表格数据。二维数组的定义方式如下:

type arrayName[size1][size2];

示例

int matrix[3][4];  // 定义一个 3 行 4 列的整数矩阵

初始化二维数组

int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

13.6 动态数组

动态数组允许在运行时定义数组大小。可以使用指针和动态内存分配( new)来实现:

int* dynamicArray = new int[size];  // 创建动态数组

使用完后,需要释放内存:

delete[] dynamicArray;  // 释放动态数组的内存

13.7 C++ 中数组详解

概念描述
多维数组C++ 支持多维数组。多维数组最简单的形式是二维数组。
指向数组的指针您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。
传递数组给函数您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
从函数返回数组C++ 允许从函数返回数组。

14. 字符串

C++ 中主要有两种字符串类型:C 风格字符串(以 null 结尾的字符数组)和 C++ 标准库中的 std::string 类。

14.1 C 风格字符串

C 风格字符串是以 char 类型数组的形式表示的,字符串的结束由一个 null 字符( \0)标识。
定义和初始化:

char str1[] = "Hello, World!";
char str2[20]; // 空数组,长度为20
strcpy(str2, str1); // 复制字符串

长度计算:
使用 strlen 函数计算字符串长度(不包括 \0)。

size_t len = strlen(str1); // len = 13

字符串连接:
使用 strcat 函数连接两个字符串。

strcat(str2, " How are you?"); // str2 现在是 "Hello, World! How are you?"

字符串比较:
使用 strcmp 函数比较两个字符串。

int result = strcmp(str1, str2); // 结果为负数、零或正数

14.2 C++ std::string 类

更类似与Java
C++ 的标准库提供了 std::string 类,封装了字符串的基本操作,并提供了更为安全和方便的方法。
定义和初始化:

std::string str1 = "Hello, World!";
std::string str2; // 空字符串
str2 = str1; // 赋值

长度和容量:
使用 size() 或 length() 获取字符串的长度。

size_t len = str1.size(); // len = 13

字符串连接:
使用 + 运算符或 append() 方法连接字符串。

std::string str3 = str1 + " How are you?";
str1.append(" Welcome!"); // str1 现在是 "Hello, World! Welcome!"

字符串比较:
使用 == 运算符比较字符串。

if (str1 == str3) {
    // Do something
}

访问字符: 使用 [] 运算符或 at() 方法访问字符串中的字符。

char ch = str1[0]; // 'H'
char ch2 = str1.at(1); // 'e'

子字符串: 使用 substr() 方法获取子字符串。

std::string sub = str1.substr(0, 5); // "Hello"

查找和替换: 使用 find() 和 replace() 方法查找和替换字符串。

查找和替换:
使用 find() 和 replace() 方法查找和替换字符串。

字符串转换: std::to\_string() 方法将其他类型转换为字符串。

int num = 42;
std::string numStr = std::to_string(num); // "42"

14.3 总结

特性C 风格字符串C++ std::string 类
定义和初始化char str[] = "text";std::string str = "text";
长度计算strlen(str);str.size();
字符串连接strcat(dest, src);str1 + str2; 或 str1.append();
字符串比较strcmp(str1, str2);str1 == str2;
访问字符str[i];str[i] 或 str.at(i);
子字符串无str.substr(pos, len);
查找和替换strstr(str, "sub");str.find("sub"); 和 str.replace();
安全性需要手动管理自动管理内存
支持动态大小否是

15. 指针(重要)

C++中的指针是一个非常重要的概念,它允许程序员直接操作内存,提供了强大的灵活性和效率。

15.1 指针的定义

指针是一个变量,它存储了另一个变量的地址。指针本身也占用内存,但它所指向的内容可能在其他地方。这种机制使得指针在动态内存管理、数组处理和数据结构实现(如链表、树等)中非常有用。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。

15.2 指针的基本语法(!!!必会!!!)

声明指针:要声明一个指针,使用 \* 符号。例如:

int *p;  // p 是一个指向 int 类型的指针

初始化指针:指针可以通过取地址运算符 & 来初始化,例如:

int a = 10;
int *p = &a;  // p 现在指向变量 a 的地址

解引用指针:通过解引用运算符 * 可以访问指针所指向的值:

int value = *p;  // value 现在等于 a 的值,即 10

15.3 指针的类型

基本数据类型指针:

  • int*:指向整数类型的指针
  • float*:指向浮点类型的指针
  • char*:指向字符类型的指针

数组指针:指向数组的指针,指向数组的首元素:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // p 指向 arr 的首元素

函数指针:指向函数的指针,可以用于回调等功能:

void func() {
    // 函数体
}
void (*funcPtr)() = func;  // funcPtr 是指向 func 的指针

15.4 指针的操作

指针算术运算:可以对指针进行加法和减法运算,指针的加法和减法是基于指针类型大小的。例如:

int arr[3] = {10, 20, 30};
int *p = arr;     // p 指向 arr 的第一个元素
p++;              // p 现在指向 arr[1],即 20

指针与动态内存分配:C++使用 new 和 delete 运算符进行动态内存管理:

int *p = new int;     // 动态分配一个整数
*p = 42;              // 设置其值为 42
delete p;            // 释放内存

int *arr = new int[5]; // 动态分配一个整数数组
delete[] arr;         // 释放数组内存

15.5 常量指针与指针常量

常量指针( const 指针):指针所指向的内容是常量,不能被修改:

int a = 10;
const int *p = &a;  // p 是指向常量的指针

指针常量:指针本身是常量,不能改变其指向,但可以修改其指向的值:

int a = 10;
int *const p = &a;  // p 是常量指针,不能改变 p 的地址

15.6 指针的使用场景

  • 动态内存分配:通过指针可以动态分配内存,适用于需要在运行时决定内存大小的场景。
  • 数据结构:指针是实现链表、树、图等数据结构的基础。
  • 函数参数传递:通过指针可以实现参数的引用传递,允许函数修改传入的参数。

15.7 注意事项

  • 空指针:指针在初始化时如果不指向任何有效地址,应该设置为 nullptr,以避免悬空指针的问题:
int *p = nullptr; // p 不指向任何有效内存
  • 内存泄漏:使用动态内存分配时,确保在不再需要时释放内存,以避免内存泄漏。
  • 指针的生命周期:指针指向的内存必须在指针使用期间保持有效,避免访问已释放的内存。

15.8 示例代码

#include <iostream>
using namespace std;

int main() {
    int a = 5;
    int *p = &a; // p 指向 a 的地址

    cout << "a 的值: " << a << endl; // 输出: a 的值: 5
    cout << "p 指向的值: " << *p << endl; // 输出: p 指向的值: 5

    *p = 10; // 通过指针修改 a 的值
    cout << "修改后的 a 的值: " << a << endl; // 输出: 修改后的 a 的值: 10

    return 0;
}

16. 引用

在 C++ 中,引用是一个别名,用于给一个已有变量起一个新的名字。引用在声明时使用 & 符号。例如:

int a = 10;
int &ref = a;  // ref 是 a 的引用

引用的特点

  1. 必须初始化:引用在创建时必须绑定到一个已有的变量。
  2. 不可改变:一旦引用被初始化为某个变量,就不能再改变引用指向其他变量。
  3. 简洁性:引用提供了一种更简洁的语法,使用引用时不需要解引用运算符 \*。

指针的特点

  1. 可以为空:指针可以不指向任何有效地址(即为空指针)。
  2. 可以改变:指针可以重新指向其他变量。
  3. 需要解引用:使用指针时,访问指针指向的值需要使用解引用运算符 \*。

引用与指针的区别

特性引用指针
初始化必须在声明时初始化可在任何时间初始化
是否可改变一旦绑定无法更改可以更改指向的地址
空值不能为 null可以为 null
语法简洁性更简洁,无需解引用需要使用解引用运算符 \*
内存管理无法直接操作内存地址可通过指针直接操作内存

示例代码

#include <iostream>
using namespace std;

void demonstrate() {
    int a = 10;
    
    // 使用引用
    int &ref = a;  // 引用
    ref = 20;      // 修改 a 的值
    cout << "a: " << a << ", ref: " << ref << endl;  // 输出: a: 20, ref: 20

    // 使用指针
    int *ptr = &a;  // 指针
    *ptr = 30;      // 修改 a 的值
    cout << "a: " << a << ", ptr: " << *ptr << endl;  // 输出: a: 30, ptr: 30
}

int main() {
    demonstrate();
    return 0;
}

图示:

引用
+------+         +------+
|  a   | ----->  | ref  |
+------+         +------+
  (10)             (10)

指针
+------+         +-------+
|  a   | ----->  |  ptr  |
+------+         +-------+
  (10)             (地址)
                 *

17. 日期 & 时间

C++ 对日期和时间的处理主要通过标准库 <chrono> 和 <ctime> 来实现。这些库提供了对时间的表示、操作和格式化等功能。

17.1 标准库

<chrono> 是 C++11 引入的时间库,提供了高精度的时间测量和时间点的表示。该库中的主要组件包括时间点、持续时间和时钟。

时间点 ( std::chrono::time_point)

时间点表示某一特定时刻,通常与某个时钟(例如,系统时钟)相关联。
定义:

std::chrono::time_point<std::chrono::system_clock> tp;

获取当前时间点:

auto now = std::chrono::system_clock::now();

持续时间 ( std::chrono::duration)

持续时间表示时间段的长度,可以用于表示时间间隔。
定义:

std::chrono::duration<int> d; // 以秒为单位
std::chrono::duration<double> d_seconds; // 以秒为单位,使用浮点数

常见的持续时间单位:

  • std::chrono::seconds
  • std::chrono::milliseconds
  • std::chrono::microseconds
  • std::chrono::nanoseconds

时钟 ( std::chrono::clock)

C++ 提供了三种类型的时钟,适用于不同的用途。

  • 系统时钟 ( std::chrono::system_clock): 表示系统当前时间,可以获取当前的实际时间(如 UTC 时间)。
  • 高分辨率时钟 ( std::chrono::high_resolution_clock): 提供最精确的时间测量,通常用于性能测试。
  • 稳定时钟 ( std::chrono::steady_clock): 一种不受系统时间调整影响的时钟,适合测量时间间隔。

17.2 基本用法示例

获取当前时间并格式化输出

#include <iostream>
#include <chrono>
#include <ctime>

int main() {
    // 获取当前时间
    auto now = std::chrono::system_clock::now();
    
    // 转换为 std::time_t
    std::time_t current_time = std::chrono::system_clock::to_time_t(now);
    
    // 输出当前时间
    std::cout << "Current time: " << std::ctime(&current_time);
    
    return 0;
}

计算时间间隔

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    
    // 模拟一些操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "Operation duration: " << duration.count() << " ms" << std::endl;

    return 0;
}

17.3 标准库

<ctime> 是 C 和 C++ 中用于处理日期和时间的传统库,提供了一些经典的时间函数。

主要类型

  • **std::time\_t**: 表示从 1970 年 1 月 1 日 00:00:00 UTC 到某个时间点的秒数。
  • **std::tm**: 结构体,用于表示分解的时间,包括年、月、日、时、分、秒等字段。

主要函数

  • **std::time()**: 获取当前的 std::time_t 类型的时间。
  • **std::localtime()**: 将 std::time_t 转换为 std::tm,表示当地时间。
  • **std::strftime()**: 将 std::tm 格式化为字符串。

17.4 示例代码

#include <iostream>
#include <ctime>

int main() {
    // 获取当前时间
    std::time_t now = std::time(nullptr);
    
    // 转换为当地时间
    std::tm *local_time = std::localtime(&now);
    
    // 格式化输出
    char buffer[80];
    std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);
    
    std::cout << "Current local time: " << buffer << std::endl;

    return 0;
}

17.5 总结

C++ 提供了强大的日期和时间处理功能,现代 C++ 推荐使用 <chrono> 库进行高精度的时间测量和便捷的时间处理。对于需要与 C 兼容的场景,可以使用 <ctime> 库。以下是一些注意事项:

  • 高精度计时: 使用 high_resolution_clock 获取高精度时间。

  • 时间转换: 在 chrono 和 ctime 之间转换时需要注意类型的匹配。

  • 格式化输出: strftime 函数提供了灵活的格式化能力,可以根据需要定制输出格式。


18. 基本的输入输出

C++ 中的基本输入输出操作主要通过标准输入输出库来实现。

C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。

18.1 I/O 库头文件

头文件函数和描述
<iostream>该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
<iomanip>该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。
<fstream>该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。

18.2 标准输出流(cout)

预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的,如下所示:

#include <iostream>
 
using namespace std;
 
int main( )
{
   char str[] = "Hello C++";
 
   cout << "Value of str is : " << str << endl;
}

输出

Value of str is : Hello C++

C++ 编译器根据要输出变量的数据类型,选择合适的流插入运算符来显示值。<< 运算符被重载来输出内置类型(整型、浮点型、double 型、字符串和指针)的数据项。
流插入运算符 << 在一个语句中可以多次使用,如上面实例中所示,endl 用于在行末添加一个换行符。

18.3 标准输入流(cin)

预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:

#include <iostream>
 
using namespace std;
 
int main( )
{
   char name[50];
 
   cout << "请输入您的名称: ";
   cin >> name;
   cout << "您的名称是: " << name << endl;
 
}

输出:

请输入您的名称: cplusplus
您的名称是: cplusplus

18.4 标准错误流(cerr)

预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准输出设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
cerr 也是与流插入运算符 << 结合使用的,如下所示:

#include <iostream>
 
using namespace std;
 
int main( )
{
   char str[] = "Unable to read....";
 
   cerr << "Error message : " << str << endl;
}

输出

Error message : Unable to read....

18.5 标准日志流(clog)

预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准输出设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。
clog 也是与流插入运算符 << 结合使用的,如下所示:

#include <iostream>
 
using namespace std;
 
int main( )
{
   char str[] = "Unable to read....";
 
   clog << "Error message : " << str << endl;
}

输出

Error message : Unable to read....

通过这些小实例,我们无法区分 cout、cerr 和 clog 的差异,但在编写和执行大型程序时,它们之间的差异就变得非常明显。所以良好的编程实践告诉我们,使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。


19. 结构体(struct)

这里与Java对比着介绍,假设你有Java基础,不做进一步深究。

19.1 C++ 中的结构体( struct)

定义与特点

  • 定义:在 C++ 中, struct 是一种用户定义的数据类型,用于组合不同类型的数据(成员变量)。
  • 默认访问修饰符:C++ 中的结构体默认的访问修饰符是 public,这意味着结构体的成员对外部代码是可见的。
  • 成员函数:C++ 中的结构体不仅可以包含数据成员,还可以包含成员函数。这使得 struct 和 class 在语法上非常相似。

示例

#include <iostream>
#include <string>

struct Person {
    std::string name; // 成员变量
    int age;         // 成员变量

    // 成员函数
    void introduce() {
        std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person; // 创建结构体对象
    person.name = "Alice"; // 赋值
    person.age = 30;       // 赋值
    person.introduce();    // 调用成员函数
    return 0;
}

19.2 Java 中的类(Class)

定义与特点

  • 定义:Java 中没有 struct 的概念,但可以使用 class 来实现相似的功能。 class 是创建对象的蓝图,包含数据和方法。
  • 默认访问修饰符:Java 中的类默认的访问修饰符是 package-private,成员可被同一包内的其他类访问。
  • 成员函数:Java 中的类可以包含方法,支持封装和继承等面向对象特性。

示例

public class Person {
    // 成员变量
    private String name;
    private int age;

    // 构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 成员方法
    public void introduce() {
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
    }

    public static void main(String[] args) {
        Person person = new Person("Alice", 30); // 创建对象
        person.introduce(); // 调用成员方法
    }
}

19.3 C++ 结构体与 Java 类的比较

特性C++ 结构体( struct)Java 类( class)
访问修饰符默认 public默认 package-private
继承支持单继承,使用 : 符号支持单继承,使用 extends 关键字
构造函数可定义必须定义(无默认构造函数时)
成员函数可以包含函数必须使用方法
支持多态使用虚函数和继承使用接口和继承
内存管理由程序员控制垃圾回收机制
默认成员访问控制publicpackage-private

20. vector 容器

C++的 vector 容器是 STL(标准模板库)中的一个重要部分,它提供了一种动态数组的实现,允许用户以灵活的方式管理存储的元素。 vector 容器具备许多强大的特性,使其在各种应用场景中非常有用。

20.1 基本特性

  • 动态大小:与传统数组不同, vector 可以根据需要动态调整大小。用户无需在编译时指定大小。
  • 连续内存: vector 在内存中存储元素时是连续的,这使得其在访问元素时具有良好的性能,尤其是在使用索引时。
  • 支持多种类型: vector 可以存储任意类型的元素,包括基本数据类型、对象、指针等。

20.2 常用操作

创建和初始化

#include <vector>

std::vector<int> v1;                // 创建一个空的 vector
std::vector<int> v2(10);            // 创建一个包含 10 个默认值 (0) 的 vector
std::vector<int> v3(10, 5);         // 创建一个包含 10 个值为 5 的 vector
std::vector<int> v4{1, 2, 3, 4, 5}; // 使用初始化列表创建 vector

添加和删除元素

  • 添加元素:使用 push_back() 方法在 vector 的末尾添加元素。
  • 删除元素:使用 pop_back() 方法从末尾删除元素,使用 erase() 方法删除指定位置的元素。
v1.push_back(10); // 在末尾添加 10
v1.push_back(20); // 在末尾添加 20
v1.pop_back();    // 删除最后一个元素 (20)
v1.erase(v1.begin()); // 删除第一个元素 (10)

访问元素

  • 使用下标运算符 []。
  • 使用 at() 方法,它会进行边界检查。
  • 使用 front() 和 back() 方法获取第一个和最后一个元素。
int first = v1[0];        // 访问第一个元素
int second = v1.at(1);    // 访问第二个元素,并检查边界
int front = v1.front();   // 获取第一个元素
int back = v1.back();     // 获取最后一个元素

20.3 迭代器

for (auto it = v1.begin(); it != v1.end(); ++it) {
    std::cout << *it << " "; // 遍历并打印每个元素
}

还可以使用范围 for 循环简化遍历:

for (const auto& value : v1) {
    std::cout << value << " ";
}

20.4 容量和大小

vector 提供了许多与容量和大小相关的方法:

  • size():返回当前元素的数量。
  • capacity():返回当前分配的容量(可以容纳的元素数量)。
  • resize(n):改变 vector 的大小到 n,如果 n 大于当前大小,新元素会被初始化。
  • reserve(n):请求改变 vector 的容量为 n,但不会改变 size()。
size_t currentSize = v1.size();
size_t currentCapacity = v1.capacity();
v1.resize(15); // 将大小改变为 15
v1.reserve(30); // 保证至少可以容纳 30 个元素

20.5 性能

  • 随机访问:由于 vector 内部是连续存储的,随机访问的时间复杂度为 O(1)。
  • 插入和删除:在末尾添加或删除元素的时间复杂度为 O(1),而在中间插入或删除元素的时间复杂度为 O(n)。
  • 内存管理:当 vector 增加大小时,它可能会重新分配内存,因此使用 reserve() 可以避免不必要的内存重新分配,提高性能。

20.6 使用示例

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers;

    // 添加元素
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    // 访问和打印元素
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除最后一个元素
    numbers.pop_back();

    // 打印剩余元素
    std::cout << "After pop_back: ";
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

21. 数据结构

以下是关于C++中几种常见数据结构的详细介绍:

21.1 数组(Array)

数组是存储在连续内存位置的一组相同类型的数据。数组的大小在创建时固定。
特点:

  • 索引访问:可以通过索引快速访问元素,时间复杂度为O(1)。
  • 固定大小:在声明时需要指定大小,不能动态改变。
  • 内存连续:所有元素在内存中是连续存储的,便于缓存。

示例代码:

int arr[5] = {1, 2, 3, 4, 5};

21.2 结构体(Struct)

结构体是C++提供的一个用户定义的数据类型,可以将不同类型的数据组合在一起。
特点:

  • 自定义类型:允许用户定义一个新的数据类型,包含不同类型的成员。
  • 公有成员:默认情况下,结构体的成员是公有的。

示例代码:

struct Person {
    std::string name;
    int age;
};

21.3 类(Class)

类是C++的核心特性之一,提供了封装数据和方法的机制,支持面向对象编程。
特点:

  • 封装:可以将数据和方法组合在一起。
  • 私有成员:默认情况下,类的成员是私有的,外部无法直接访问。
  • 继承和多态:支持继承、重载等面向对象特性。

示例代码:

class Animal {
private:
    std::string name;

public:
    void setName(std::string n) {
        name = n;
    }
    std::string getName() {
        return name;
    }
};

补充:C++ 中类(class)和结构(struct)之间区别

特性类(Class)结构(Struct)
默认访问修饰符私有(private)公有(public)
成员函数可以有成员函数可以有成员函数
继承支持继承支持继承
构造函数/析构函数可以定义构造函数和析构函数可以定义构造函数和析构函数
封装支持封装支持封装
多态性支持虚函数,实现多态支持虚函数,实现多态
用途通常用于表示抽象数据类型通常用于表示简单的数据结构
使用习惯常用于复杂对象和行为的建模常用于简单数据的集合或记录

21.4 链表(Linked List)

链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。
特点:

  • 动态大小:可以动态增加或减少元素。
  • 插入和删除:在链表中插入或删除元素效率高,时间复杂度为O(1)(给定节点指针)。
  • 随机访问:访问元素时需要遍历,时间复杂度为O(n)。

示例代码:

struct Node {
    int data;
    Node* next;
};

Node* head = nullptr;  // 链表头

21.5 栈(Stack)

栈是一种后进先出(LIFO)的数据结构,支持在一端插入和删除元素。
特点:

  • 操作限制:只能在栈顶插入或删除元素。
  • 常用操作: push(入栈)、 pop(出栈)、 top(查看栈顶元素)。
  • 应用:用于函数调用管理、表达式求值等。

示例代码:

#include <stack>
std::stack<int> s;
s.push(1);
s.pop();

21.6 队列(Queue)

队列是一种先进先出(FIFO)的数据结构,元素在一端插入,在另一端删除。
特点:

  • 操作限制:只能在队列尾插入元素,在队列头删除元素。
  • 常用操作: enqueue(入队)、 dequeue(出队)、 front(查看队头元素)。
  • 应用:任务调度、广度优先搜索等。

示例代码:

#include <queue>
std::queue<int> q;
q.push(1);
q.pop();

21.7 双端队列(Deque)

双端队列是一种可以在两端插入和删除元素的数据结构。
特点:

  • 灵活性:支持在两端操作,既可作为栈也可作为队列。
  • 常用操作: push\_front、 push\_back、 pop\_front、 pop\_back。
  • 应用:缓存管理、任务调度等。

示例代码:

#include <deque>
std::deque<int> d;
d.push_back(1);
d.push_front(2);
d.pop_back();

21.8 哈希表(Hash Table)

哈希表是根据键值对(key-value)存储数据的数据结构,使用哈希函数将键映射到存储位置。
特点:

  • 快速访问:平均时间复杂度为O(1)。
  • 冲突处理:使用链表或开放寻址法处理哈希冲突。
  • 应用:快速查找、数据库索引等。

示例代码:

#include <unordered_map>
std::unordered_map<int, std::string> hashMap;
hashMap[1] = "one";

21.9 映射(Map)

映射是一种键值对集合,允许根据键快速查找对应的值。
特点:

  • 排序:标准库中的 std::map 是有序的,而 std::unordered\_map 是无序的。
  • 键唯一:每个键只能对应一个值。
  • 应用:存储关联数据。

示例代码:

#include <map>
std::map<int, std::string> myMap;
myMap[1] = "one";

21.10 集合(Set)

集合是一种存储唯一元素的数据结构,不允许重复。
特点:

  • 唯一性:集合中的每个元素都是唯一的。
  • 自动排序: std::set 自动对元素进行排序。
  • 应用:集合运算、去重。

示例代码:

#include <set>
std::set<int> mySet;
mySet.insert(1);
mySet.insert(1);  // 不会重复插入

21.11 动态数组(Vector)

动态数组是一种可以动态调整大小的数组,提供随机访问。
特点:

  • 动态大小:在插入或删除元素时,可以自动扩展或收缩大小。
  • 随机访问:支持快速索引访问,时间复杂度为O(1)。
  • 应用:用于存储动态大小的数据。

示例代码:

#include <vector>
std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);

总结
C++提供了多种数据结构,每种结构都有其独特的特点和应用场景。在实际编程中,选择合适的数据结构对于提高程序的效率和可读性至关重要。理解这些基本数据结构是掌握C++编程的基础。


22. 最后

OK!C语言和C++基础到此为止,接下来就专心搞JNI和音视频相关的东西,说实话,搞这两篇文章废了老长时间,要是单纯想回顾一下,根本没必要写成文章,写文章的目的一方面方便查阅,另一方面成就自己的同时帮助别人,而我都乐在其中。

最后,借用下菜鸟教程的话:学的不仅仅是知识,更是梦想。祝大家早日圆梦!