C++

本贴最后更新于 181 天前,其中的信息可能已经时移世易

C++ 是一种系统编程语言。用它的发明者, Bjarne Stroustrup 的话来说,C++ 的设计目标是:

  • 成为“更好的 C 语言”
  • 支持数据的抽象与封装
  • 支持面向对象编程
  • 支持泛型编程

C++ 提供了对硬件的紧密控制(正如 C 语言一样), 能够编译为机器语言,由处理器直接执行。 与此同时,它也提供了泛型、异常和类等高层功能。 虽然 C++ 的语法可能比某些出现较晚的语言更复杂,它仍然得到了人们的青睞—— 功能与速度的平衡使 C++ 成为了目前应用最广泛的系统编程语言之一。

////////////////
// 与C语言的比较
////////////////

// C++_几乎_是C语言的一个超集,它与C语言的基本语法有许多相同之处,
// 例如变量和函数的声明,原生数据类型等等。

// 和C语言一样,在C++中,你的程序会从main()开始执行,
// 该函数的返回值应当为int型,这个返回值会作为程序的退出状态值。
// 不过,大多数的编译器(gcc,clang等)也接受 void main() 的函数原型。
// (参见 http://en.wikipedia.org/wiki/Exit_status 来获取更多信息)
int main(int argc, char** argv)
{
    // 和C语言一样,命令行参数通过argc和argv传递。
    // argc代表命令行参数的数量,
    // 而argv是一个包含“C语言风格字符串”(char *)的数组,
    // 其中每个字符串代表一个命令行参数的内容,
    // 首个命令行参数是调用该程序时所使用的名称。
    // 如果你不关心命令行参数的值,argc和argv可以被忽略。
    // 此时,你可以用int main()作为函数原型。

    // 退出状态值为0时,表示程序执行成功
    return 0;
}

// 然而,C++和C语言也有一些区别:

// 在C++中,字符字面量的大小是一个字节。
sizeof('c') == 1

// 在C语言中,字符字面量的大小与int相同。
sizeof('c') == sizeof(10)


// C++的函数原型与函数定义是严格匹配的
void func(); // 这个函数不能接受任何参数

// 而在C语言中
void func(); // 这个函数能接受任意数量的参数

// 在C++中,用nullptr代替C语言中的NULL
int* ip = nullptr;

// C++也可以使用C语言的标准头文件,
// 但是需要加上前缀“c”并去掉末尾的“.h”。
#include <cstdio>

int main()
{
    printf("Hello, world!\n");
    return 0;
}

///////////
// 函数重载
///////////

// C++支持函数重载,你可以定义一组名称相同而参数不同的函数。

void print(char const* myString)
{
    printf("String %s\n", myString);
}

void print(int myInt)
{
    printf("My int is %d", myInt);
}

int main()
{
    print("Hello"); // 解析为 void print(const char*)
    print(15); // 解析为 void print(int)
}

///////////////////
// 函数参数的默认值
///////////////////

// 你可以为函数的参数指定默认值,
// 它们将会在调用者没有提供相应参数时被使用。

void doSomethingWithInts(int a = 1, int b = 4)
{
    // 对两个参数进行一些操作
}

int main()
{
    doSomethingWithInts();      // a = 1,  b = 4
    doSomethingWithInts(20);    // a = 20, b = 4
    doSomethingWithInts(20, 5); // a = 20, b = 5
}

// 默认参数必须放在所有的常规参数之后。

void invalidDeclaration(int a = 1, int b) // 这是错误的!
{
}


///////////
// 命名空间
///////////

// 命名空间为变量、函数和其他声明提供了分离的的作用域。
// 命名空间可以嵌套使用。

namespace First {
    namespace Nested {
        void foo()
        {
            printf("This is First::Nested::foo\n");
        }
    } // 结束嵌套的命名空间Nested
} // 结束命名空间First

namespace Second {
    void foo()
    {
        printf("This is Second::foo\n")
    }
}

void foo()
{
    printf("This is global foo\n");
}

int main()
{
    // 如果没有特别指定,就从“Second”中取得所需的内容。
    using namespace Second;

    foo(); // 显示“This is Second::foo”
    First::Nested::foo(); // 显示“This is First::Nested::foo”
    ::foo(); // 显示“This is global foo”
}

////////////
// 输入/输出
////////////

// C++使用“流”来输入输出。<<是流的插入运算符,>>是流提取运算符。
// cin、cout、和cerr分别代表
// stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。

#include <iostream> // 引入包含输入/输出流的头文件

using namespace std; // 输入输出流在std命名空间(也就是标准库)中。

int main()
{
   int myInt;

   // 在标准输出(终端/显示器)中显示
   cout << "Enter your favorite number:\n";
   // 从标准输入(键盘)获得一个值
   cin >> myInt;

   // cout也提供了格式化功能
   cout << "Your favorite number is " << myInt << "\n";
   // 显示“Your favorite number is <myInt>”

   cerr << "Used for error messages";
}

/////////
// 字符串
/////////

// C++中的字符串是对象,它们有很多成员函数
#include <string>

using namespace std; // 字符串也在std命名空间(标准库)中。

string myString = "Hello";
string myOtherString = " World";

// + 可以用于连接字符串。
cout << myString + myOtherString; // "Hello World"

cout << myString + " You"; // "Hello You"

// C++中的字符串是可变的,具有“值语义”。
myString.append(" Dog");
cout << myString; // "Hello Dog"


/////////////
// 引用
/////////////

// 除了支持C语言中的指针类型以外,C++还提供了_引用_。
// 引用是一种特殊的指针类型,一旦被定义就不能重新赋值,并且不能被设置为空值。
// 使用引用时的语法与原变量相同:
// 也就是说,对引用类型进行解引用时,不需要使用*;
// 赋值时也不需要用&来取地址。

using namespace std;

string foo = "I am foo";
string bar = "I am bar";


string& fooRef = foo; // 建立了一个对foo的引用。
fooRef += ". Hi!"; // 通过引用来修改foo的值
cout << fooRef; // "I am foo. Hi!"

// 这句话的并不会改变fooRef的指向,其效果与“foo = bar”相同。
// 也就是说,在执行这条语句之后,foo == "I am bar"。
fooRef = bar;

const string& barRef = bar; // 建立指向bar的常量引用。
// 和C语言中一样,(指针和引用)声明为常量时,对应的值不能被修改。
barRef += ". Hi!"; // 这是错误的,不能修改一个常量引用的值。

///////////////////
// 类与面向对象编程
///////////////////

// 有关类的第一个示例
#include <iostream>

// 声明一个类。
// 类通常在头文件(.h或.hpp)中声明。
class Dog {
    // 成员变量和成员函数默认情况下是私有(private)的。
    std::string name;
    int weight;

// 在这个标签之后,所有声明都是公有(public)的,
// 直到重新指定“private:”(私有继承)或“protected:”(保护继承)为止
public:

    // 默认的构造器
    Dog();

    // 这里是成员函数声明的一个例子。
    // 可以注意到,我们在此处使用了std::string,而不是using namespace std
    // 语句using namespace绝不应当出现在头文件当中。
    void setName(const std::string& dogsName);

    void setWeight(int dogsWeight);

    // 如果一个函数不对对象的状态进行修改,
    // 应当在声明中加上const。
    // 这样,你就可以对一个以常量方式引用的对象执行该操作。
    // 同时可以注意到,当父类的成员函数需要被子类重写时,
    // 父类中的函数必须被显式声明为_虚函数(virtual)_。
    // 考虑到性能方面的因素,函数默认情况下不会被声明为虚函数。
    virtual void print() const;

    // 函数也可以在class body内部定义。
    // 这样定义的函数会自动成为内联函数。
    void bark() const { std::cout << name << " barks!\n" }

    // 除了构造器以外,C++还提供了析构器。
    // 当一个对象被删除或者脱离其定义域时,它的析构函数会被调用。
    // 这使得RAII这样的强大范式(参见下文)成为可能。
    // 为了衍生出子类来,基类的析构函数必须定义为虚函数。
    virtual ~Dog();

}; // 在类的定义之后,要加一个分号

// 类的成员函数通常在.cpp文件中实现。
void Dog::Dog()
{
    std::cout << "A dog has been constructed\n";
}

// 对象(例如字符串)应当以引用的形式传递,
// 对于不需要修改的对象,最好使用常量引用。
void Dog::setName(const std::string& dogsName)
{
    name = dogsName;
}

void Dog::setWeight(int dogsWeight)
{
    weight = dogsWeight;
}

// 虚函数的virtual关键字只需要在声明时使用,不需要在定义时重复
void Dog::print() const
{
    std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";
}

void Dog::~Dog()
{
    std::cout << "Goodbye " << name << "\n";
}

int main() {
    Dog myDog; // 此时显示“A dog has been constructed”
    myDog.setName("Barkley");
    myDog.setWeight(10);
    myDog.print(); // 显示“Dog is Barkley and weighs 10 kg”
    return 0;
} // 显示“Goodbye Barkley”

// 继承:

// 这个类继承了Dog类中的公有(public)和保护(protected)对象
class OwnedDog : public Dog {

    void setOwner(const std::string& dogsOwner)

    // 重写OwnedDogs类的print方法。
    // 如果你不熟悉子类多态的话,可以参考这个页面中的概述:
    // http://zh.wikipedia.org/wiki/%E5%AD%90%E7%B1%BB%E5%9E%8B

    // override关键字是可选的,它确保你所重写的是基类中的方法。
    void print() const override;

private:
    std::string owner;
};

// 与此同时,在对应的.cpp文件里:

void OwnedDog::setOwner(const std::string& dogsOwner)
{
    owner = dogsOwner;
}

void OwnedDog::print() const
{
    Dog::print(); // 调用基类Dog中的print方法
    // "Dog is <name> and weights <weight>"

    std::cout << "Dog is owned by " << owner << "\n";
    // "Dog is owned by <owner>"
}

/////////////////////
// 初始化与运算符重载
/////////////////////

// 在C++中,通过定义一些特殊名称的函数,
// 你可以重载+、-、*、/等运算符的行为。
// 当运算符被使用时,这些特殊函数会被调用,从而实现运算符重载。

#include <iostream>
using namespace std;

class Point {
public:
    // 可以以这样的方式为成员变量设置默认值。
    double x = 0;
    double y = 0;

    // 定义一个默认的构造器。
    // 除了将Point初始化为(0, 0)以外,这个函数什么都不做。
    Point() { };

    // 下面使用的语法称为初始化列表,
    // 这是初始化类中成员变量的正确方式。
    Point (double a, double b) :
        x(a),
        y(b)
    { /* 除了初始化成员变量外,什么都不做 */ }

    // 重载 + 运算符
    Point operator+(const Point& rhs) const;

    // 重载 += 运算符
    Point& operator+=(const Point& rhs);

    // 增加 - 和 -= 运算符也是有意义的,但这里不再赘述。
};

Point Point::operator+(const Point& rhs) const
{
    // 创建一个新的点,
    // 其横纵坐标分别为这个点与另一点在对应方向上的坐标之和。
    return Point(x + rhs.x, y + rhs.y);
}

Point& Point::operator+=(const Point& rhs)
{
    x += rhs.x;
    y += rhs.y;
    return *this;
}

int main () {
    Point up (0,1);
    Point right (1,0);
    // 这里使用了Point类型的运算符“+”
    // 调用up(Point类型)的“+”方法,并以right作为函数的参数
    Point result = up + right;
    // 显示“Result is upright (1,1)”
    cout << "Result is upright (" << result.x << ',' << result.y << ")\n";
    return 0;
}

///////////
// 异常处理
///////////

// 标准库中提供了一些基本的异常类型
// (参见http://en.cppreference.com/w/cpp/error/exception)
// 但是,其他任何类型也可以作为一个异常被拋出
#include <exception>

// 在_try_代码块中拋出的异常可以被随后的_catch_捕获。
try {
    // 不要用 _new_关键字在堆上为异常分配空间。
    throw std::exception("A problem occurred");
}
// 如果拋出的异常是一个对象,可以用常量引用来捕获它
catch (const std::exception& ex)
{
  std::cout << ex.what();
// 捕获尚未被_catch_处理的所有错误
} catch (...)
{
    std::cout << "Unknown exception caught";
    throw; // 重新拋出异常
}

///////
// RAII
///////

// RAII指的是“资源获取就是初始化”(Resource Allocation Is Initialization),
// 它被视作C++中最强大的编程范式之一。
// 简单说来,它指的是,用构造函数来获取一个对象的资源,
// 相应的,借助析构函数来释放对象的资源。

// 为了理解这一范式的用处,让我们考虑某个函数使用文件句柄时的情况:
void doSomethingWithAFile(const char* filename)
{
    // 首先,让我们假设一切都会顺利进行。

    FILE* fh = fopen(filename, "r"); // 以只读模式打开文件

    doSomethingWithTheFile(fh);
    doSomethingElseWithIt(fh);

    fclose(fh); // 关闭文件句柄
}

// 不幸的是,随着错误处理机制的引入,事情会变得复杂。
// 假设fopen函数有可能执行失败,
// 而doSomethingWithTheFile和doSomethingElseWithIt会在失败时返回错误代码。
// (虽然异常是C++中处理错误的推荐方式,
// 但是某些程序员,尤其是有C语言背景的,并不认可异常捕获机制的作用)。
// 现在,我们必须检查每个函数调用是否成功执行,并在问题发生的时候关闭文件句柄。
bool doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
    if (fh == nullptr) // 当执行失败是,返回的指针是nullptr
        return false; // 向调用者汇报错误

    // 假设每个函数会在执行失败时返回false
    if (!doSomethingWithTheFile(fh)) {
        fclose(fh); // 关闭文件句柄,避免造成内存泄漏。
        return false; // 反馈错误
    }
    if (!doSomethingElseWithIt(fh)) {
        fclose(fh); // 关闭文件句柄
        return false; // 反馈错误
    }

    fclose(fh); // 关闭文件句柄
    return true; // 指示函数已成功执行
}

// C语言的程序员通常会借助goto语句简化上面的代码:
bool doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r");
    if (fh == nullptr)
        return false;

    if (!doSomethingWithTheFile(fh))
        goto failure;

    if (!doSomethingElseWithIt(fh))
        goto failure;

    fclose(fh); // 关闭文件
    return true; // 执行成功

failure:
    fclose(fh);
    return false; // 反馈错误
}

// 如果用异常捕获机制来指示错误的话,
// 代码会变得清晰一些,但是仍然有优化的余地。
void doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
    if (fh == nullptr)
        throw std::exception("Could not open the file.");

    try {
        doSomethingWithTheFile(fh);
        doSomethingElseWithIt(fh);
    }
    catch (...) {
        fclose(fh); // 保证出错的时候文件被正确关闭
        throw; // 之后,重新抛出这个异常
    }

    fclose(fh); // 关闭文件
    // 所有工作顺利完成
}

// 相比之下,使用C++中的文件流类(fstream)时,
// fstream会利用自己的析构器来关闭文件句柄。
// 只要离开了某一对象的定义域,它的析构函数就会被自动调用。
void doSomethingWithAFile(const std::string& filename)
{
    // ifstream是输入文件流(input file stream)的简称
    std::ifstream fh(filename); // 打开一个文件

    // 对文件进行一些操作
    doSomethingWithTheFile(fh);
    doSomethingElseWithIt(fh);

} // 文件已经被析构器自动关闭

// 与上面几种方式相比,这种方式有着_明显_的优势:
// 1. 无论发生了什么情况,资源(此例当中是文件句柄)都会被正确关闭。
//    只要你正确使用了析构器,就_不会_因为忘记关闭句柄,造成资源的泄漏。
// 2. 可以注意到,通过这种方式写出来的代码十分简洁。
//    析构器会在后台关闭文件句柄,不再需要你来操心这些琐事。
// 3. 这种方式的代码具有异常安全性。
//    无论在函数中的何处拋出异常,都不会阻碍对文件资源的释放。

// 地道的C++代码应当把RAII的使用扩展到各种类型的资源上,包括:
// - 用unique_ptr和shared_ptr管理的内存
// - 各种数据容器,例如标准库中的链表、向量(容量自动扩展的数组)、散列表等;
//   当它们脱离作用域时,析构器会自动释放其中储存的内容。
// - 用lock_guard和unique_lock实现的互斥

基础

基本框架(不管写什么先写这十行)

#include<iostream>
using namespace std;

int main(){
  

  
    system("pause");
    return 0;
}

Hello World

#include<iostream>
using namespace std;

int main(){
  
    cout << "Hello World" << endl;
  
    system("pause");
    return 0;
}

数据类型

整数

// 短整型
short a = 1;
// 整型
int b = 1;
// 长整型
long c = 1;
// 长长整型
long long d = 1;

浮点型

// 单精度浮点型
float a = 1.1;
// 双精度浮点型
double b = 1.1;

字符型

// 字符型
char a = '字';
// 字符串型
char b[] = "字符串";

字符串

#include<iostream>
// 使用string需要导入头文件
#include<string>
using namespace std;

int main(){
    string a = "Hello World";
      
    cout << a << endl;
  
    system("pause");
    return 0;
}

布尔型

#include<iostream>
using namespace std;

int main(){
    bool a = true;
      
    cout << a << endl;
  
    system("pause");
    return 0;
}

转义字符

作用:用于表示一些不能显示出来的 ASCII 字符

常用的转义字符:\n \\ \t

转义字符 含义 ASCII 码表
\a 警报 007
\b 退格 008
\f 换页 012
\n 换行 010
\r 回车 013
\t 水平制表(HT)(跳到下一个 TAB(8 个空格)位置) 009
\v 垂直制表 011
\\ 代表一个反斜线字符 092
' 代表单引号字符 039
" 代表双引号字符 034
? 代表问号 063
\0 数字 0 000
\ddd 8 进制转义字符,d 范围 0~7 3 位 8 进制
\xhh 16 进制转义字符,h 范围 09,af,A~F 3 位 16 进制

查询大小

siziof 关键字:查询变量内存占用

#include<iostream>
using namespace std;

int main(){
    char a[] = "Hello World"
  
    // siziof关键字:查询变量内存占用
    cout << "a变量占用内存大小为:" << sizeof(a) << endl;
  
    system("pause");
    return 0;
}

数据键盘输入

cin 关键字:从键盘获取一个整数

#include<iostream>
using namespace std;

int main(){
    int a = 0;
  
    cout << "输入a的值" << endl;
    // 关键字为cin
    cin >> a;
    cout << a << endl;
  
    system("pause");
    return 0;
}

运算符

算数运算符

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 取模,整除后的余数
++ 自增
-- 自减

赋值运算符

运算符 描述
=
把右边操作数的值赋给左边操作数
+= 把右边操作数加上左边操作数的结果赋值给左边操作数
-= 把左边操作数减去右边操作数的结果赋值给左边操作数
*= 把右边操作数乘以左边操作数的结果赋值给左边操作数
/= 把左边操作数除以右边操作数的结果赋值给左边操作数
%= 求两个操作数的模赋值给左边操作数
<<= 左移且赋值运算符
>>= 右移且赋值运算符
&= 按位与且赋值运算符
^= 按位异或且赋值运算符
|= 按位或且赋值运算符

比较运算符

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

逻辑运算符

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

if 结构

if else if else 语句

#include<iostream>
using namespace std;

int main(){
    int a = 0;
  
    if(a < 0){
        cout << "a小于0" << endl;
    }else if(a == 0){
        cout << "a等于0" << endl;
    }else{
        cout << "a大于0" << endl;
    }
  
    system("pause");
    return 0;
}

三元运算符

#include<iostream>
using namespace std;

int main(){
    int a = 0;
  
    cout << a==0? "a等于0":"a不等于0" << endl;

    system("pause");
    return 0;
}

switch 语句

#include<iostream>
using namespace std;

int main(){
    int a = 0;
  
    cout << a==0? "a等于0":"a不等于0" << endl;

    system("pause");
    return 0;
}

循环语句

While 循环

#include<iostream>
using namespace std;

int main(){
    int a = 0;
  
    while(a <= 10){
        cout << a << endl;
        a++;
    }

    system("pause");
    return 0;
}

do While 循环

#include<iostream>
using namespace std;

int main(){
    int a = 0;
  
    do{
        cout << a << endl;
        a++;
    }while(a <= 10)

    system("pause");
    return 0;
}

for 循环

#include<iostream>
using namespace std;

int main(){
  
    for(int i = 0; i < 10; i++){
        cout << a << endl;
    }

    system("pause");
    return 0;
}

数组

同类型数据的集合

  • 每个数据元素都是相同的数据类型
  • 是由连续的内存位置组成的

一维数组

格式

数据类型 数组名称[数组长度];
数据类型 数组名称[数组长度] = {1, 2, 3, ...};
数据类型 数组名称[] = {1, 2, 3, ...};

示范

int a[10];
int b[3] = {1, 2, 3};
int c[] = {1, 2, 3, 4};

二维数组

格式

数据类型 数组名称[行数][列数];
数据类型 数组名称[行数][列数] = {{1, 2, 3, ...},{1, 2, 3, ...}};
数据类型 数组名称[][列数] = {{1, 2, 3, ...},{1, 2, 3, ...}};

示范

省行不省列

int a[10][10];
int b[2][3] = {{1, 2, 3},{1, 2, 3}};
int c[][3] = {1, 2, 3, 4, 5, 6};

函数

格式

返回值类型 函数名称(参数1, 参数2, ...){
	代码
	return 返回值
}

写法

int sum(int a, int b){
	return a + b;
}

调用

#include<iostream>
using namespace std;

int sum(int a, int b){
    int sum = a + b;
	return sum;
}

int main(){
  
    // 调用函数,返回两个参数之和并打印
    cout << sum(1, 2) << endl;

    system("pause");
    return 0;
}

指针

可以通过指针间接访问内存,指针就是一个存放地址的变量

数据类型 * 指针变量名称



#include<iostream>
using namespace std;

int main(){
    int a = 10; 

    // 创建指针并记录a的内存地址
    int * p = &a;

    // 使用指针,解引用(*p等于内存地址的值,也就是a的值)
    *p = 1000;

    // 修改内存地址的值也等于修改变量a的值
	cout << *p << endl;		// 结果:1000,a也是1000
  
    system("pause");
    return 0;
}

常量修饰符

在创建指针时加上 const 修饰符,表示这个指针不能被改变

// 常量指针,值不能改,地址能改
const int * p = &;
// 指针常量,值能改,地址不能改
int * const p = &;
// 常量,值不能改,地址不能改
const int * const p = &;

空指针

指的是指针创建时为 NULL,空指针不允许被访问

int * p = NULL;

野指针

指针指向非法内存空间,直接操作非法空间可能没用权限,或引发不可预料的情况

int * p = (int*)0x1100;

指针数组

数组在内存地址中是连续的,可以通过指针来访问数组

#include<iostream>
using namespace std;

int main(){
  
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 指针指向列表第一位
    int * p = arr;
    cout << *p << endl;
    // 指针内存地址向前一位
    p++;		// 指针向后偏移4个字节
    // 此时访问到列表第二位
    cout << *p << endl;
  
    system("pause");
    return 0;
}

指针函数

函数的参数可以传入指针

#include<iostream>
using namespace std;

void swap(int * p1, int * p2){
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

int main(){
  
    int a = 10;
    int b = 20;
  
    swap(&a, &b);
  
    cout << a << endl;
    cout << b << endl;
  
    system("pause");
    return 0;
}

结构体

属于用户自定义的数据类型(内置的数据类型的集合组成的新类型),相当于类的前身

结构体基本不用,完全被 class 取代了

struct 结构体名称{
    数据类型 变量名称;
    数据类型 变量名称;
    ...
};




// 创建结构体
struct Struct{
    int a;
    float b;
    char c;
}

// 创建结构体,顺便实例
struct Struct{
    int a;
    float b;
    char c;
}s1;

// 使用1,struct关键字可省略
struct Struct s1 = {1, 1.1, "123"};

// 使用2,struct关键字可省略
struct Struct s2;
s2.a = 1;
s2.b = 1.1;
s2.c = "123";

常量修饰符

在创建指针时加上 const 修饰符,表示这个结构体不能被改变

// 创建结构体
struct Struct{
    int a;
    float b;
    char c;
}

// 实例结构体
const struct Struct s = { 1, 1.1, "111"}

结构体数组

将结构体放进数组中

// 创建结构体
struct Struct{
    int a;
    float b;
    char c;
}
// 用数组方式实例结构体
struct Struct stuarr[3] = {
    {1, 1.1, "111"},
    {2, 2.2, "122"},
    {3, 3.3, "133"}
}

// 通过下标修改结构体属性
stuarr[0].a = 0

结构体指针

通过指针操作结构体

  • 通过 ​->​ 操作符访问结构体指针的属性
// 创建结构体
struct Struct{
    int a;
    float b;
    char c;
};
// 实例结构体
struct Struct s = {1, 1.1, "123"};
// 创建结构体指针指向s结构体
struct * p = &s;
// 通过->来访问结构体指针的属性
cout << p->a << endl;
cout << p->b << endl;
cout << p->c << endl;

结构体嵌套

struct Struct0{
    int aa;
    float bb;
    char cc;
};

struct Struct1{
    int a;
    float b;
    char c;
    struct Struct0 stu;
};

// 实例结构体
Struct1 s;
// 修改结构体
s.a = 1;
s.b = 1.1;
s.c = "111";
// 修改结构体内的结构体
s.stu.aa = 2;
s.stu.bb = 2.2;
s.stu.cc = "222"

结构体函数

函数的参数可以传入结构体

#include<iostream>
using namespace std;

struct Struct{
    int a;
    float b;
    char c;
};

void swap(struct Struct s){
	cout << s.a << s.b << s.c << endl;
}

int main(){
  
    struct Struct0 s;
    s.a = 1;
    s.b = 1.1;
    s.c = "111";
  
    swap(s);
      
    system("pause");
    return 0;
}

new 操作符

利用 new 在堆区开辟数据,new 返回的是数据的指针

new 数据类型

引用

给变量取别名

格式

数据类型 &变量别名 = 变量原名

写法

int a = 10;
int &b = a;


// 创建函数,参数是引用
void function(int &x, int &y)
{
	// 变量被重新赋值
	x = 10;
	y = 20;
}

void main()
{
	int a = 1;
	int b = 2;

	// 传入变量
	function(a, b);
	cout << a << endl << b << endl;

	// 结果是输入10和20,a和b的值被函数内改变
}

不使用引用

// 创建函数,参数是引用
void function(int &x, int &y)
{
	// 变量被重新赋值
	x = 10;
	y = 20;
}

void main()
{
	int a = 1;
	int b = 2;

	// 传入变量
	function(a, b);
	cout << a << endl << b << endl;

	// 结果是输入10和20,a和b的值被函数内改变
}

进阶

面向对象、class

类是代码的设计图,对象是按照类生成出来的代码,我们一般把生成过程叫做实例化

然而这个设计图,是可以被其它设计图复制然后加以改进的,这个复制的过程就是继承

然而有些类我们希望他有很多特性,继承了非常多类,这个行为叫做多态

在实际中,被复制的设计图就是父类,复制其它设计图的设计图就是子类

类都是设计图,只有在实例化成为对象时,这个代码才运行,才可以用它自身的属性和方法

类的结构

class 类名称{
    权限:
    	变量 or 函数(方法)
    权限:
    	变量 or 函数(方法)
    ...
}
#include<iostream>
using namespace std;

// 创建类
class ClassName{
    // 权限
    public:
        // 类变量(成员变量)
        int attribute = 1;
        // 类方法(成员方法)
        void function(int a) 
        {
            attribute = a;
        }
};

int main() {
	// 实例化类
    ClassName cln;
	// 调用成员属性
    cout << cln.attribute << endl;
    // 调用成员方法
    cln.function(10);
    cout << cln.attribute << endl;

    system("pause");
    return 0;
}

权限

  • public(公开)
    在类外面可以被直接调用
  • protected(保护)
    在类外面可以不可以被调用,只有子类(继承这个类的类)才可以调用
  • private(私有)
    只能在类里面被调用
// 创建类
class ClassName{
    // 公开,在类外面可以被直接调用
    public:
        变量 or 函数(方法);
    // 保护,在类外面可以不可以被调用,只有子类(继承这个类的类)才可以调用
    protected:
    	变量 or 函数(方法);
    // 私有,只能在类里面被调用
    private:
    	变量 or 函数(方法);
};

构造函数和析构函数

在类中,有两个隐藏的函数可以编写,功能分别是类在实例后自动调用一次,和类在销毁前自动调用一次

class ClassName{
    // 与类名一致,且没有返回值的函数就是构造函数
    // 类在实例后会自动调用一次,也就是初始化
    ClassName(){
        ...
    };
    // 与类名一致,前面加上~,且没有返回值的函数就是析构函数
    // 类在销毁前会自动调用一次
    // 注意!不允许有参数
    ~ClassName(){
        ...
    }
  
};

静态成员

定义一个全局变量,所有对象都可以访问这个变量

// 创建静态成员
static int a = 0;
static void func(){
    ...
}
// 访问方式
类名::静态成员名称

继承

继承某个类的属性或方法

class 子类 : 继承方式 父类{
    ...
};



// ClassB继承ClassA类
class ClassB : public ClassA{
    ...
};

继承方式示例代码:公有继承,保护继承,私有继承

// 创建类
class ClassA
{
public:
	int a = 1;
protected:
	int b = 2;
private:
	int c = 3;
};

// 继承ClassA类的公有属性和受保护属性,但无法继承父类的私有属性
// 将继承到的属性变为自身类的public公有属性,可以被自身外调用
class ClassB : public ClassA
{
public:
	// 构造函数,在实例化类时打印自身属性或方法
	ClassB()
	{
		cout << a << endl;
		cout << b << endl;
	}
};

void main()
{
	// 实例化ClassB类,触发构造函数
	ClassB classb;

	// 公有继承可以在类外调用属性
	classb.a;
	classb.b;
}
// 创建类
class ClassA
{
public:
	int a = 1;
protected:
	int b = 2;
private:
	int c = 3;
};

// 继承ClassA类的公有属性和受保护属性,但无法继承父类的私有属性
// 将继承到的属性变为自身类的protected受保护属性,除了子类,不允许在类外调用
class ClassB : protected ClassA
{
public:
	// 构造函数,在实例化类时打印自身属性或方法
	ClassB()
	{
		cout << a << endl;
		cout << b << endl;
	}
};

void main()
{
	// 实例化ClassB类,触发构造函数
	ClassB classb;

	// 无法在类外调用受保护的属性
}
// 创建类
class ClassA
{
public:
	int a = 1;
protected:
	int b = 2;
private:
	int c = 3;
};

// 继承ClassA类的公有属性和受保护属性,但无法继承父类的私有属性
// 将继承到的属性变为自身类的private私有属性,在类外不允许被调用
class ClassB : private ClassA
{
public:
	// 构造函数,在实例化类时打印自身属性或方法
	ClassB()
	{
		cout << a << endl;
		cout << b << endl;
	}
};

void main()
{
	// 实例化ClassB类,触发构造函数
	ClassB classb;

	// 无法在类外调用私有的属性
}

多态

多态主要是使用虚函数,让子类可以重写指定的成员函数

虚函数

********************************格式:
virtual void function()


********************************示范:
class Class
{
public:
	virtual void function()
	{
		cout << "Class" << endl;
	}
};


********************************不使用多态:
// 创建类
class ClassA
{
public:
	void function()
	{
		cout << "ClassA" << endl;
	}
};

// 创建子类并重写函数
class ClassB : public ClassA
{
public:
	void function()
	{
		cout << "ClassB" << endl;
	}
};

void main()
{
	// 父类类型指针传入子类,并调用函数,结果使用的是父类的函数
	// 函数不会被重写,需要使用virtual虚函数
	ClassA *Class = new ClassB;
	Class->function();
}


********************************使用多态:
// 创建类
class ClassA
{
public:
	virtual void function()
	{
		cout << "ClassA" << endl;
	}
};

// 创建子类并重写函数
class ClassB : public ClassA
{
public:
	void function()
	{
		cout << "ClassB" << endl;
	}
};

void main()
{
	// 父类类型指针传入子类,并调用函数,结果使用的是子类的函数
	// 使用了virtual后,函数被重写
	ClassA *Class = new ClassB;
	Class->function();
}

纯虚函数

指定一个函数作为纯虚函数,作用是给子类去重写,不实现逻辑

类中有纯虚函数,这个类就是抽象类,是用来给子类去重写其中函数

子类必须重写纯虚函数,否则无法实例化类

********************************格式:
virtual 返回类型 函数名(参数列表) = 0;


********************************写法:
virtual void function() = 0;


********************************示范:
// 创建类
class ClassA
{
public:
	// 定义纯虚函数
	virtual void function() = 0;
};

// 继承并重写纯虚函数
class ClassB : public ClassA
{
public:
	void function()
	{
		cout << "ClassB" << endl;
	}
};

void main()
{
	// 报错! ClassA无法被实例化,因为类中有纯虚函数,是抽象类
	ClassA Clsa;
	Clsa.function();

	// 因为子类重写了纯虚函数,所以可以直接被调用
	ClassB Clsb;
	Clsb.function();
}

友元

指定函数或类可以访问自身的私有属性

目的就是让一个函数或者类,访问另一个类中的私有变量

********************************格式:
class ClassA
{
    friend 函数定义
    friend 类定义
}


********************************写法:
class ClassA
{
	// 指定全局函数为友元,友元可访问私有属性a
	friend viod function();
	// 指定类为友元,友元可访问私有属性a
	friend class ClassB;
private:
	int a;
}

运算符重载

对运算符重新定义,赋予其它功能,以适应不同数据类型,例如时间 + 时间。

通过固定形式的函数来定义运算符重载

格式

函数返回类型 operator 运算符(参数)
{
	函数处理
}

类内成员函数写法

class ClassA
{
public:
	int a = 0;
	int operator+(int i)
	{
		return this->a + i;
	}
};

int main()
{
	ClassA A;
	cout << A+10+10 << endl;
}

友元全局函数写法

class ClassA
{
friend int operator+(ClassA &A, int i);

public:
	int a = 0;
};

int operator+(ClassA &A, int i)
{
	return A.a + i;
}

int main()
{
	ClassA A;
	cout << A + 10 << endl;
}

文件操作

C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:

  • ifstream​:专用于从文件中读取数据;
  • ofstream:专用于向文件中写入数据;
  • fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。

头文件

#include<fstream>

写入文件

#include<fstream>

using namespace std;

void main()
{
	// 创建文件流类
	fstream f;
	// 打开指定文件
	f.open("123.txt");
	// 输入内容
	f << "123";
	// 读取内容

	// 关闭文件流
	f.close();
}

读取文件

#include<fstream>

using namespace std;

void main()
{
	// 创建文件流类
	fstream f;
	string content;
	// 打开指定文件
	f.open("123.txt");
	// 将内容保存至变量
	f >> content;
	// 关闭文件流
	f.close();
	// 打印内容
	cout << content;
}
成员函数 功能
open() 打开指定文件,使其与文件流对象相关联。
is_open() 检查指定文件是否已打开。
close() 关闭文件,切断和文件流对象的关联。
swap() 交换 2 个文件流对象。
operator>> 重载 >> 运算符,用于从指定文件中读取数据。
gcount() 返回上次从文件流提取出的字符个数。该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。
get() 从文件流中读取一个字符,同时该字符会从输入流中消失。
getline(str,n,ch) 从文件流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 ‘\0’。
ignore(n,ch) 从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。
peek() 返回文件流中的第一个字符,但并不是提取该字符。
putback© 将字符 c 置入文件流(缓冲区)。
operator<< 重载 << 运算符,用于向文件中写入指定数据。
put() 向指定文件流中写入单个字符。
write() 向指定文件中写入字符串。
tellp() 用于获取当前文件输出流指针的位置。
seekp() 设置输出文件输出流指针的位置。
flush() 刷新文件输出流缓冲区。
good() 操作成功,没有发生任何错误。
eof() 到达输入末尾或文件尾。

模板

类模板

建立一个通用类,类中的数据类型在实例化时再制定

格式

template<typename T>
class cls
{
	T name;
};

示例代码

// 创建类模板
template<typename T>
class cls
{
public:
	T name = 1;
};

void main()
{
	// 实例化类,定义T的类型为int
	cls<int> c;
	// 打印变量的值和类型
	cout << c.name << endl << typeid(c.name).name() << endl;
}

函数模板

建立一个通用函数,在实例化时再制定数据类型

格式

template<typename T>
void func(T &a)
{
	cout << typeid(a).name() << endl;
}

示例代码

// 创建模板
template<typename T>
void func(T &a)
{
	// 打印参数数据类型
	cout << typeid(a).name() << endl;
}

void main()
{
	// 创建int变量
	int a = 1;
	// 使用模板函数,定义int类型,传入int变量
	func<int>(a);
}

标准模板库(STL)

STL(Standard Template Library) 标准模板库

STL 是 C++ 开发过程中常用的数据结构和算法库,以往编程中没有这套标准,每次开发都需要写一套数据结构和算法,直到 1998 年惠普实验室开发了这套库,至今已被内置到 C++ 中变为标准库,无需额外安装。

功能上来说,就是 C++ 内置的数据结构和算法。

Array(静态数组)

固定容量的数组,不可延长/缩短数组长度

头文件

#include<array>

创建静态数组

#include<array>

void main()
{
	// 创建数组,指定数据类型与长度
	array<int, 5> a;
}

遍历数组

#include<array>

using namespace std;

void main()
{
	// 创建数组
	array<int, 5> a = {1, 2, 3, 4, 5};
	// 遍历数组
	for (int i : a)
	{
		cout << i << endl;
	}
}
成员函数 功能
begin() 返回指向容器中第一个元素的随机访问迭代器。
end() 返回指向容器最后一个元素之后一个位置的随机访问迭代器,通常和 begin() 结合使用。
rbegin() 返回指向最后一个元素的随机访问迭代器。
rend() 返回指向第一个元素之前一个位置的随机访问迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size() 返回容器中当前元素的数量,其值始终等于初始化 array 类的第二个模板参数 N。
max_size() 返回容器可容纳元素的最大数量,其值始终等于初始化 array 类的第二个模板参数 N。
empty() 判断容器是否为空,和通过 size()==0 的判断条件功能相同,但其效率可能更快。
at(n) 返回容器中 n 位置处元素的引用,该函数自动检查 n 是否在有效的范围内,如果不是则抛出 out_of_range 异常。
front() 返回容器中第一个元素的直接引用,该函数不适用于空的 array 容器。
back() 返回容器中最后一个元素的直接应用,该函数同样不适用于空的 array 容器。
data() 返回一个指向容器首个元素的指针。利用该指针,可实现复制容器中所有元素等类似功能。
fill(val) 将 val 这个值赋值给容器中的每个元素。
array1.swap(array2) 交换 array1 和 array2 容器中的所有元素,但前提是它们具有相同的长度和类型。

Vector (动态数组)

动态容量的数组,可动态延长/缩短数组长度

头文件

#include<vector>

创建动态数组

#include<vector>

void main()
{
	// 普通创建
	vector<int> v;
	// 指定长度创建
	vector<int> v(5);
	// 指定数据创建
	vector<int> v{1, 2, 3};
	// 嵌套创建
	vector< vector<int> > v;
}

遍历数组

#include<vector>

void main()
{
	// 创建动态数组
	vector<int> v{1, 2, 3};
	// 遍历数组
	for (auto i : v)
	{
		cout << i << endl;
	}
}
成员函数 功能
begin() 返回指向容器中第一个元素的迭代器。
end() 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
rbegin() 返回指向最后一个元素的迭代器。
rend() 返回指向第一个元素所在位置前一个位置的迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size() 返回实际元素个数。
max_size() 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
resize() 改变实际元素的个数。
capacity() 返回当前容量。
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
reserve() 增加容器的容量。
shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
operator[ ] 重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。
at() 使用经过边界检查的索引访问元素。
front() 返回第一个元素的引用。
back() 返回最后一个元素的引用。
data() 返回指向容器中第一个元素的指针。
assign() 用新元素替换原有内容。
push_back() 在序列的尾部添加一个元素。
pop_back() 移出序列尾部的元素。
insert() 在指定的位置插入一个或多个元素。
erase() 移出一个元素或一段元素。
clear() 移出所有的元素,容器大小变为 0。
swap() 交换两个容器的所有元素。
emplace() 在指定的位置直接生成一个元素。
emplace_back() 在序列尾部生成一个元素。

Queue (单向队列)

只能在一端输入数据,另一端输出数据的列表

头文件

#include<queue>

创建单向队列

#include<queue>

void main()
{
	// 创建单向队列
	queue<int> q;
}

遍历队列

#include<queue>

void main()
{
	// 创建单向队列
	queue<int> q;
	// 插入数据
	q.push(1);
	q.push(2);
	q.push(3);
	// 遍历数据,检测队列是否有数据
	while (!q.empty())
	{
		// 打印队列首位数据
		cout << q.front() << endl;
		// 删除队列首位数据
		q.pop();
	}
}
成员函数 功能
empty() 如果 queue 中没有元素的话,返回 true。
size() 返回 queue 中元素的个数。
front() 返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
back() 返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
push(const T& obj) 在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。
emplace() 在 queue 的尾部直接添加一个元素。
push(T&& obj) 以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。
pop() 删除 queue 中的第一个元素。
​​swap(queue &other_queue)​ 将两个 queue 容器适配器中的元素进行互换,需要注意的是,进行互换的 2 个 queue 容器适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。

Deque (双向队列)

在两端都可以进行输入输出数据的列表

头文件

#include<deque>

创建双向队列

#include<deque>

void main()
{
	// 创建单向队列
	deque<int> dq;
}

遍历队列

#include<deque>

void main()
{
	// 创建双向队列
	deque<int> d;

	// 向队列头部添加数据
	d.push_front(1);
	d.push_front(2);
	d.push_front(3);

	// 向队列尾部添加数据
	d.push_back(1);
	d.push_back(2);
	d.push_back(3);

	// 遍历队列
	for (int i = 0; i < d.size(); i++)
	{
		cout << d.at(i) << endl;
	}
}
成员函数 功能
begin() 返回指向容器中第一个元素的迭代器。
end() 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
rbegin() 返回指向最后一个元素的迭代器。
rend() 返回指向第一个元素所在位置前一个位置的迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size() 返回实际元素个数。
max_size() 返回容器所能容纳元素个数的最大值。这通常是一个很大的值,一般是 232-1,我们很少会用到这个函数。
resize() 改变实际元素的个数。
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
at() 使用经过边界检查的索引访问元素。
front() 返回第一个元素的引用。
back() 返回最后一个元素的引用。
assign() 用新元素替换原有内容。
push_back() 在序列的尾部添加一个元素。
push_front() 在序列的头部添加一个元素。
pop_back() 移除容器尾部的元素。
pop_front() 移除容器头部的元素。
insert() 在指定的位置插入一个或多个元素。
erase() 移除一个元素或一段元素。
clear() 移出所有的元素,容器大小变为 0。
swap() 交换两个容器的所有元素。
emplace() 在指定的位置直接生成一个元素。
emplace_front() 在容器头部生成一个元素。和 push_front() 的区别是,该函数直接在容器头部构造元素,省去了复制移动元素的过程。
emplace_back() 在容器尾部生成一个元素。和 push_back() 的区别是,该函数直接在容器尾部构造元素,省去了复制移动元素的过程。

Stack (栈)

只能在一端进行输入和输出的列表

头文件

#include<stack>

创建栈

#include<stack>

void main()
{
	// 创建单向队列
	stack<int> s;
}

遍历栈

#include<stack>

void main()
{
	// 创建栈
	stack<int> s;

	// 添加数据
	s.push(1);
	s.push(2);
	s.push(3);

	// 遍历栈,检测栈是否为空
	while (!s.empty())
	{
		// 打印栈顶
		cout << s.top() << endl;
		// 删除栈顶
		s.pop();
	}
}
成员函数 功能
empty() 当 stack 栈中没有元素时,该成员函数返回 true;反之,返回 false。
size() 返回 stack 栈中存储元素的个数。
top() 返回一个栈顶元素的引用,类型为 T&。如果栈为空,程序会报错。
push(const T& val) 先复制 val,再将 val 副本压入栈顶。这是通过调用底层容器的 push_back() 函数完成的。
push(T&& obj) 以移动元素的方式将其压入栈顶。这是通过调用底层容器的有右值引用参数的 push_back() 函数完成的。
pop() 弹出栈顶元素。
emplace(arg…) arg… 可以是一个参数,也可以是多个参数,但它们都只用于构造一个对象,并在栈顶直接生成该对象,作为新的栈顶元素。
​​swap(stack & other_stack)​ 将两个 stack 适配器中的元素进行互换,需要注意的是,进行互换的 2 个 stack 适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。

Map (字典)

map 容器存储的都是一一对应的数据,就像字典一样

头文件

#include<map>

创建字典

#include<map>

void main()
{
	// 创建字典
	map<string, int> s;
}

遍历字典

#include<map>

void main()
{
	// 创建字典
	map<string, int> m;
	// 插入键值对
	m.insert({"str", 1})

	for (auto i = m.begin(); i != m.end(); i++)
	{
		// 打印键
		cout << i->first << endl;
		// 打印值
		cout << i->second << endl;
	}
}
成员函数 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 map 容器中存有键值对的个数。
max_size() 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
operator[] map 容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。
at(key) 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。
insert() 向 map 容器中插入键值对。
erase() 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。
swap() 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
clear() 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。
emplace() 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
count(key) 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。

Set (集合)

一个不重复且排好序的列表

头文件

#include<set>

创建集合

#include<set>

void main()
{
	// 创建集合
	set<string, int> s;
}

遍历集合

#include<set>

void main()
{
	// 初始赋值
	set<int> s{1, 2, 3};

	// 遍历集合
	for (auto i = s.begin(); i != s.end(); i++)
	{
		cout << *i << endl;
	}
}
成员函数 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 set 容器中存有元素的个数。
max_size() 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 set 容器中插入元素。
erase() 删除 set 容器中存储的元素。
swap() 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
clear() 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
emplace() 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。

内置算法

C++ 内置的算法,算法是解决问题的方法,常用的有排序、搜索、分组、比较,在开发中经常用到。

头文件

#include <algorithm>

排序算法

在编程中最常用的是排序算法,

函数名 用法
sort (first, last) 对 first-last 中的元素进行排序,默认进行升序排序
stable_sort (first, last) 和 sort() 函数功能相似,不同之处在于该函数不会改变相同元素的相对位置
partial_sort (first, middle, last) 从 first-last 中内筛选出 muddle-first 中最小的元素并排序存放在 first-middle 中
partial_sort_copy (first, last, result_first, result_last) 与 partial_sort 相似,区别在于会先拷贝容器再排序
is_sorted (first, last) 检测 first-last 中的元素是否已经排好序,默认检测是否按升序排序。
is_sorted_until (first, last) 和 is_sorted() 函数功能类似,区别在于会返回第一个不符合的位置
void nth_element (first, nth, last) 找到 [first, last) 范围内按照排序规则(默认按照升序排序)应该位于第 nth 个位置处的元素,并将其放置到此位置。同时使该位置左侧的所有元素都比其存放的元素小,该位置右侧的所有元素都比其存放的元素大。

查找算法

C++ 内置的常用函数,用来搜索数组、Array、Vector、Deque 的元素

函数名 用法
find(first, last, val) 查找 first-last 之间 val 的位置
find_end(first1, last1, first2, last2) 查找 first2-last2 在 first1-last1 最后出现的位置
find_if(first, last, function) 根据 function 的规则查找 first-last 中的元素

分组算法

可根据用户自定义的筛选规则,重新排列指定区域内存储的数据

函数名 用法
partition(first, last, function) 将符合 function 规则的元素排在前面,但不排序
stable_partition(first, last, function) 将符合 function 规则的元素排在前面并排序
partition_copy((first, last, result_true, result_false, function)) 根据 function 的规则查找 first-last,将符合 function 规则的放置在 result_true 中,不符合的放置在 result_false 中
partition_point(first, last, function) 根据 function 的规则查找 first-last 中第一个不符合规则的元素位置

其它关键词

asm

在 C++ 内部嵌入汇编指令

int main()
{
	asm("汇编指令")
}

inline

用来表示内联函数,提升函数被多次调用时的效率,省去调用函数的开销,一般用于代码较少的函数

inline void sum(int a, int b)
{
	return a + b;
}

explicit

explicit 只对类的构造函数有效,用来防止构造函数进行隐式类型转换

class ClassA
{
public:
	int _a;
	explicit ClassA(int a){ _a = a; }
};

int main()
{
	ClassA a(9);
	// ClassA b = 9; 报错,不允许隐式类型转换
}

mutable

用来表示可变数据,一般用于 const 对象,定义 mutable 后,可以在 const 中修改数据

class ClassA
{
public:
	mutable int a;
};

int main()
{
	const ClassA A;
	A.a = 10;
}

namespace

用来定义命名空间,一般用来避免工程较大时产生的名称冲突,只能在全局中定义

namespace ns
{
	int a;
}

int main()
{
	// 使用命名空间
	ns::a = 1;

	// 引用命名空间,在接下来可以省略命名空间直接调用数据
	using namespace ns;

	a = 1;
}

register

用来修饰变量,将变量直接寄存在 CPU 中,提升运行效率

int main()
{
	register int a;
}

volatile

用来修饰对象,声明后会让编译器必须每次重新读取这个对象,不会假定他的值

如果不声明,这个对象假如被系统、硬件、汇编改变,编译器还是会按照他“假定”的值来计算

一般涉及硬件编程时使用

int main()
{
	volatile int a;
}
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    106 引用 • 152 回帖 • 2 关注
2 操作
ZanMei 在 2023-11-20 16:31:53 更新了该帖
ZanMei 在 2023-11-20 16:31:15 更新了该帖

相关帖子

回帖
C++

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...