快速从C过渡到C++

快速从C过渡到C++

本笔记为作者快速从C过渡到C++的笔记目的是为了更加方便的刷力扣,如有不对的地方,请包含与谅解!

------------by wsoz

Hello World程序

c++ 复制代码
#include <iostream>
using namespace std;

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

逐行解析

第1行:#include <iostream>

引入输入输出流(Input/Output Stream)头文件。

对比 C语言 C++
头文件 #include <stdio.h> #include <iostream>
输出函数 printf() cout
输入函数 scanf() cin

注意:C++标准库头文件使用尖括号 <>,不带 .h 后缀。"iostream" 虽然能用,但 <iostream> 才是规范写法。


第2行:using namespace std;

声明使用标准命名空间(namespace)。

什么是命名空间? 可以理解为"代码的分组",避免不同库中同名函数/变量冲突。coutcinendl 都定义在 std 命名空间中。

c++ 复制代码
// 不使用 using namespace std 时,需要这样写:
std::cout << "Hello" << std::endl;

// 使用后可以简写:
cout << "Hello" << endl;

类比Python:类似于 from std import *,把std里的东西都引入当前作用域。


第3行:int main()

程序入口函数,与C语言完全相同。


第5行:cout << "Hello World!" << endl;

这是C++的标准输出方式。

组成部分 含义
cout c haracter output,标准输出流对象
<< 流插入运算符,将右侧数据"流入"输出流
endl end line,换行并刷新缓冲区

与C语言对比:

c 复制代码
// C语言
printf("Hello World!\n");

// C++
cout << "Hello World!" << endl;

链式输出: << 可以连续使用

c++ 复制代码
int age = 20;
cout << "年龄:" << age << "岁" << endl;
// 输出:年龄:20岁

类比Python的 print(),但C++用 << 连接,Python用逗号或f-string。


第6行:system("pause");

调用系统命令暂停程序,仅Windows有效。作用是防止控制台窗口一闪而过

这不是C++标准,而是Windows特有的。Linux/Mac下可删除此行。


第7行:return 0;

返回0表示程序正常结束,与C语言相同。


C vs C++ 输入输出对比

功能 C语言 C++
输出字符串 printf("Hello"); cout << "Hello";
输出变量 printf("%d", num); cout << num;
输出换行 printf("\n"); cout << endl;
输入变量 scanf("%d", &num); cin >> num;

C++的优势: 不需要记格式符(%d%s%f),编译器自动识别类型。


刷算法快速入门------基础

这部分主要用于对力扣刷题进行的一个快速从C语言过渡到C++的一个笔记,我是参考B站UP主【C语言 转 C++ 简单教程】https://www.bilibili.com/video/BV1UE411j7Ti?p=2\&vd_source=100abe451980ff9dd4a5647d880af0c5

using namespace std

使用标准命名空间,为了避免每个厂商定义的命令不同因此直接使用标准的命名空间可以防止出现问题

cpp 复制代码
#include <iostream>
#include <cstring>

using namespace std;
 
int main(int argc, const char** argv) {
    if(argc < 2) {
        cout << "error" <<endl;
    }
    else
    {
        char* name = new char[100];
        cin >> name;
        int num = strlen(name);
        cout << argv[1] << ", " << name << "!" << endl;
        cout << "length of name: " << num << endl;
        delete[] name;
    }
    return 0;
}

下面进行详细的解释:

cpp 复制代码
#include <iostream>

这个头文件就相当于是 #include <stdio.h>,功能也是一样的
*

cpp 复制代码
using namespace std;

这个就是使用标准的命名空间,如果不使用标准命名空间的话那就要像下面这样使用

cpp 复制代码
std::cin>>n;
std::cout<<"hello"<<endl;

C++使用C时头文件

在C++中我们会使用到C时的API时,我们也需要包含头文件,比如 strlen() 就需要包含字符串的头文件。

cpp 复制代码
#include <cstring>

同样的,如果要使用其他的C语言中的API时,我们只需要引入头文件即可,使用 #include <cxxxxx> 即可来代替 #include <xxx.h>

当然我们如果使用自己定义的头文件时可以直接调用 #include "xxx.h"

变量声明

和C语言一样大多数都是通用的,但是在C语言中如果要使用bool类型变量,则需要额外引入布尔类型头文件,但是在C++中默认时支持布尔类型的所以直接就可以使用bool来定义变量。

string类

在C语言中,使用字符串进行表示需要通过 char*str 来进行表示,而在C++中有string类直接来表示就可以直接快速简便的对字符串进行相关的操作。

c++ 复制代码
#include<iostream>
#include<string>

using namespace std;

int main() {
    string name;
    cout << "Enter your name: ";
    cin >> name;
    string greeting = "Hello, " + name + "!";
    cout << greeting << endl;
    cout << "Length of name: " << name.length() << endl;
    return 0;
}

在C++中String是一个类,然后代码中的 name.length() 就是 类.方法() 这是面向对象编程提供的类,下面讲解一下常见的对于String类的操作:

cpp 复制代码
// 下面这些操作常用到的头文件
#include <iostream> // cin/cout/endl
#include <string>   // string/getline/stoi/stod/to_string
#include <limits>   // numeric_limits(仅"常见坑"会用到)
using namespace std;
  • 创建与赋值

    cpp 复制代码
    #include <string>
    
    string s1 = "hello";
    string s2("world");
    string s3 = s1;   // 拷贝
    s3 = "cpp";       // 重新赋值
  • 输入与输出

    需要掌握的就是 cingetline(cin, str);

    cpp 复制代码
    string name;
    cin >> name;      // 遇到空格结束
    cout << name << endl;
    cpp 复制代码
    string str;
    getline(cin, str); // 读取整行,可包含空格
  • 长度与判空

    cpp 复制代码
    cout << s1.size() << endl;    // 长度
    cout << s1.length() << endl;  // 与 size() 等价
    cout << s1.empty() << endl;   // 是否为空
  • 访问与修改字符

    cpp 复制代码
    char c1 = s1[0];      // 不做越界检查
    char c2 = s1.at(0);   // 做越界检查
    s1[0] = 'H';          // 可直接修改
  • 拼接字符串

    cpp 复制代码
    string a = "Hello";
    string b = "World";
    string c = a + ", " + b;
    a += "!";
    a.append(" C++");
  • 比较字符串

    cpp 复制代码
    if (a == b) { /* 相等 */ }
    if (a < b)  { /* 按字典序比较 */ }
    
    int r = a.compare(b); // r<0: a<b, r=0: 相等, r>0: a>b
  • 查找与截取(刷题高频)

    cpp 复制代码
    string s = "abcdefabc";
    size_t pos = s.find("abc");      // 从前往后找
    size_t pos2 = s.rfind("abc");    // 从后往前找
    
    if (pos != string::npos) {
        cout << s.substr(pos, 3) << endl; // 从 pos 开始截 3 个字符
    }
  • 插入、删除、替换

    cpp 复制代码
    string s = "abcde";
    s.insert(2, "XX");      // abXXcde
    s.erase(2, 2);          // abcde
    s.replace(1, 3, "QQ");  // aQQe
  • 与 C 风格字符串互转

    cpp 复制代码
    string s = "hello";
    const char* p = s.c_str(); // 给 C API 使用
  • 数字与字符串转换

    cpp 复制代码
    int x = stoi("123");
    double d = stod("3.14");
    string t = to_string(456);
  • 常见坑

    cpp 复制代码
    #include <iostream>
    #include <string>
    #include <limits>
    
    int n;
    string line;
    cin >> n;
    cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清掉缓冲区换行
    getline(cin, line); // 否则这里可能直接读到空串

    find() 的返回类型是 size_t,找不到时返回 string::npos

结构体

在C语言中结构体的定义是

c 复制代码
typedef struct _data{
    int value;
    char*label;
}data_t;

data_t mydata;
struct _data mydata;

在C语言中我们定义了结构体之后要使用的话

  • 可以使用 struct xxx xxx 来定义后使用
  • 也可以通过 typedef 为结构体定义别名后使用

但是在C++中则可以直接快速定义使用,可以不用使用上面的两种方法

c++ 复制代码
struct _data{
	int value;
    char*label;
};

//直接使用
_data mydata;

由此可见在C++中使用结构体则可以快速定义结构体方便使用。

引用

在C++中引用使用&符号表示,要注意区分:

  • 在声明里:int& r = a; 表示"引用"
    • 在表达式里:&a 表示"取地址"

即是在声明中表示引用,表达式中表示取地址。

引用本质上就是可以理解对于变量取别名,可以通过修改这个参数来改变原参数,先看一下在C语言中要修改原本的参数方法:

c 复制代码
#include<stdio.h>

void func(int* a)
{
    *a +=1 ;
}

int main()
{
    int a = 10;
    func(&a);
    printf("a = %d", a);
    return 0;
}

在C语言中就是使用指针的形式来修改,在C++中当然也可以使用指针的形式来进行修改,但是C++提供了更加方便的引用来修改:

cpp 复制代码
#include<iostream>

using namespace std;

void fun(int &a)
{
    a +=1 ;
}

int main()
{
    int a=10;
    fun(a);
    cout << "a = " << a << endl;
    return 0;
}

指针和引用对比:

对比维度 指针(pointer) 引用(reference)
本质 存储地址的变量 已有变量的别名
定义时是否必须初始化 是(必须绑定对象)
是否可为空 可以(nullptr 不可以(标准语义下必须绑定有效对象)
是否可改指向/改绑定 可以改指向其他对象 不可以改绑定(绑定后固定)
使用方式 需要解引用 *p 访问值 直接像普通变量一样使用
是否需要取地址传参 常见写法:func(&a) 常见写法:func(a)
sizeof 结果 指针大小(如 4/8 字节,和平台相关) 引用本身不单独占用可观察对象存储(sizeof(引用)通常等于被引用类型大小)
典型用途 动态内存、可空参数、重置指向 函数参数别名、返回别名、运算符重载等

标准模板库(STL)

C++ 的 STL(Standard Template Library,标准模板库)是一套基于模板的通用数据结构和算法的集合。它不是 C++ 标准库的全部,但构成了其中最核心、最实用的部分。

STL 帮你把常用的数据结构(如数组、链表、哈希表)和算法(如排序、查找)做成了现成的"积木块",你可以直接拿来用,无需重复造轮子。

Vector

vector就是STL中提供的动态数组,和普通数组最大的区别就是可以在使用过程中动态的扩展数组。

vector创建

以int动态数组创建时为例,主要有三种方法:

  • 利用vector v
  • 利用vector v(10)
  • 利用vector v(10,2)

下面解释一下这三种创建的方法,首先第一个就是声明了这个动态数组,但是目前还没有获取大小相当于就是简单的创建声明。

第二个则是可以看成(10,0)默认就是创建了一个大小为10的动态数组,默认值为0;所以第三个就是默认值为2。

分配vector大小

对于我们刚才用第一种方式创建的vector,因为没有分配到大小,所以可以使用 resize 来分配大小

c++ 复制代码
#include <vector>
vector <int> v;
v.resize(length);		//默认为0

当然这个函数也可以实现对于动态数组的扩展和缩小。

size、capacity、reserve、resize区别(重点)

这四个是刷题最容易混淆的,直接先记住:

  • size():当前元素个数
  • capacity():当前容量(最多可容纳多少个元素而不触发扩容)
  • reserve(n):只保证容量至少为 n不改变元素个数
  • resize(n):直接改元素个数为 n(变大补默认值,变小删尾部)

下面用一个例子快速理解:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v;
    cout << "Init: size=" << v.size() << ", capacity=" << v.capacity() << endl;

    v.reserve(10);
    cout << "After reserve(10): size=" << v.size() << ", capacity=" << v.capacity() << endl;

    v.resize(5); // add 5 elements, default value is 0
    cout << "After resize(5): size=" << v.size() << ", capacity=" << v.capacity() << endl;

    v.resize(8, 7); // grow to 8, new elements are 7
    cout << "After resize(8, 7), v[5]=" << v[5] << endl;

    v.resize(3); // shrink to 3, trailing elements are removed
    cout << "After resize(3): size=" << v.size() << ", capacity=" << v.capacity() << endl;
    return 0;
}
powershell 复制代码
Init: size=0, capacity=0
After reserve(10): size=0, capacity=10
After resize(5): size=5, capacity=10
After resize(8, 7), v[5]=7
After resize(3): size=3, capacity=10

注意一个坑:reserve 之后 size 还是0,不能直接 v[0] = 1,要先 push_backresize

其实本质上就是分清楚容量和个数的问题

  • 容量就是可以最多存的个数
  • 个数就是当前已经存的位置个数
vector末尾添加/删除

在末尾添加元素,我们可以直接调用 vector.push_back(data) 之后就可以将这个data追加到末尾。

cpp 复制代码
#include <vector>
vector <int> v(10,2);
v.push_back(10);	//最后v就是11个元素,最后一个元素是10

在末尾删除元素,我们可以直接调用 vector.pop_back() 之后就可以将这个动态数组中的末尾元素删除。

cpp 复制代码
#include <vector>
vector <int> v(10,2);
v.pop_back();	//最后元素为9个
遍历循环和迭代器

同样和C语言一样对于遍历依旧可以采用for循环,C++里面还可以使用迭代器来实现遍历

cpp 复制代码
vector <int> v(10);
for(int i=0;i<10;i++)	//赋值
{	
    v[i]=i;
}

for(auto p=v.begin();p!=v.end();p++)
{
    cout << *p << " ";
}

下面讲一下迭代器,可以理解为**"泛化指针"**,用来在容器里移动和访问元素。

即将我们的p当成指针,指针在进行移动,然后用*解引用即可。

中间插入和删除(insert/erase)

除了末尾添加删除,刷题中也经常需要在中间位置进行插入和删除,这时候就要使用 inserterase

  • insert:在指定位置前面插入元素,后面的元素整体后移
  • erase:删除指定位置(或区间)元素,后面的元素整体前移
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4};

    // 在下标2的位置插入99(也就是元素3前面)
    v.insert(v.begin() + 2, 99);   // 1 2 99 3 4

    // 删除下标1的元素(元素2)
    v.erase(v.begin() + 1);        // 1 99 3 4

    // 删除区间 [begin+1, begin+3),左闭右开
    v.erase(v.begin() + 1, v.begin() + 3); // 1 4

    for (auto x : v) cout << x << " ";
    return 0;
}

解释一下 for (auto x : v) cout << x << " "; 本质上就是将V中的元素取出来赋值给x,然后打印。

迭代器失效

当你对 vector 做插入、删除、扩容时,原来的迭代器可能会失效,最常见的就是边遍历边删除时写错。

错误写法(容易崩或跳元素):

cpp 复制代码
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 1) {
        v.erase(it); // erase后it已经失效
    }
}

正确写法(必须接住 erase 的返回值):

cpp 复制代码
for (auto it = v.begin(); it != v.end(); ) {
    if (*it % 2 == 1) {
        it = v.erase(it); // 返回下一个有效迭代器
    } else {
        ++it;
    }
}

这一点非常关键:erase 之后不要再对旧 it 做 ++it,要用返回值更新 it。

说白了就是当前位置的的地址已经被删除了,导致it失真,所以我们要利用 erase 的返回值即是移动完成之后目前动态数组这个位置的值进行后续操作。

  • erase(it):返回下一个有效元素的位置 (该位置是"后面顶上来的元素")
  • insert(pos, x):返回新插入元素的位置 (该位置是"新插入的元素")
二维vector

二维 vector 本质上就是"vector 里面再放 vector",常用于网格题、二维DP、矩阵模拟。

二维vector创建

最常用的创建方式:

cpp 复制代码
#include <vector>
using namespace std;

int n = 3, m = 4;
vector<vector<int>> g(n, vector<int>(m, 0)); // 3行4列,初始值全为0

如果你想初始化成别的值,比如 -1

cpp 复制代码
vector<vector<int>> g(n, vector<int>(m, -1));

二维vector访问与修改

访问方式和二维数组一样,直接 g[i][j]

cpp 复制代码
g[1][2] = 5;              // 修改
int x = g[1][2];          // 读取

二维vector遍历

最常见就是双层for循环:

cpp 复制代码
for (int i = 0; i < g.size(); i++) {
    for (int j = 0; j < g[i].size(); j++) {
        cout << g[i][j] << " ";
    }
    cout << endl;
}

二维vector常见坑

  • 行数是 g.size(),列数是 g[i].size(),不要写反
  • 不要在未初始化大小时直接 g[i][j] 访问(会越界)
  • 如果行列固定,优先用 vector<vector<int>>(n, vector<int>(m, val)) 一次性初始化

Set

Set集合,具有两个非常重要的特性:

  • 互斥性,即集合中的元素只能出现一次
  • 有序性,即是集合中的元素是有序的,你放入集合中会按照从小到大的顺序进行排序
Set创建

和vector一样创建就是按照 set <int> s 进行创建,但是需要注意一点集合创建后面不可以跟参数

cpp 复制代码
#include<set>
set<int> s;
Set元素添加/删除

我们创建了集合之后,就可以向这个集合对象中放入数据或者删除数据等操作,利用的方法还是set.insert(data)set.erase(data)

cpp 复制代码
set<int> s;
s.insert(1);		//插入元素1
s.erase(2);			//删除元素2

我们插入或者删除数据之后集合内部会默认进行排序。

Set元素查找

元素查找就是直接在集合中进行 set.find(data) 即可,这个方法的返回值是 迭代器(iterator)

  • 找到:返回指向该元素的迭代器
  • 找不到:返回 s.end()
cpp 复制代码
if(s.find(5) != s.end())
    cout << "5 is found in set" << endl;
else
    cout << "5 is not found in set" << endl;

也可以用 count 来判断是否存在(在 set 中返回值只会是 01):

cpp 复制代码
if (s.count(5))
    cout << "5 is found in set" << endl;
else
    cout << "5 is not found in set" << endl;
Set大小和判空

对于集合我们经常要知道当前有多少个元素,或者判断是不是空集合,这两个操作分别是:

  • set.size():返回集合中元素个数
  • set.empty():判断集合是否为空(空返回 true
cpp 复制代码
set<int> s;
s.insert(10);
s.insert(20);

cout << "size = " << s.size() << endl;   // 2
cout << s.empty() << endl;               // 0(false)
Set遍历

Set的遍历可以像vector一样都大差不差都是使用循环或者迭代器,最常用的是范围for:

cpp 复制代码
set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);

for(auto x : s)
{
    cout << x << " ";
}

解释一下 for (auto x : s) cout << x << " "; 本质上就是将V中的元素取出来赋值给x,然后打印。


Map

Map即是键值对 <key>---<value>,通常就是通过key去获取值。同时Map内部也是有序的,会按照键进行排序

Map创建

Map键值对创建采用 map<string, int> s 进行创建:

c++ 复制代码
#include <map>
#include <string>
using namespace std;

map<string, int> s;
Map键值对添加

添加键值对直接采用类似赋值的形式进行:

c++ 复制代码
map<string, int> s;
s["hello"] = 666;
// 则有  "hello"--666  的键值对
Map键值对访问

访问键值对时直接就是和数组的访问形式类似,访问时要注意:[]at() 不一样:

  • s["key"]:如果key不存在,会自动插入一个默认值
  • s.at("key"):如果key不存在,会抛出异常,不会自动插入
c++ 复制代码
int value = s["hello"];
// 此时 value = 666
Map键值对长度

获取键值对长度,其实都可以用 .size() 获取,本质就是获取容器的长度:

c++ 复制代码
s.size();
Map键值对遍历

Map键值对遍历也是采用for循环迭代器进行遍历:

c++ 复制代码
for (auto i = s.begin(); i != s.end(); i++)
{
    cout << i->first << ": " << i->second << endl;
}

简而言之你可以先把它看成结构体指针 ,通过 i->firsti->second 访问键和值:

c++ 复制代码
typedef struct kv_data{
    string first;   // key
    int second;     // value
} kv_data;
Map查找

Map查找最常用的是 findcount

  • find(key):找到返回该键的迭代器,找不到返回 s.end()
  • count(key):因为map键唯一,所以只会返回 01
c++ 复制代码
if (s.find("hello") != s.end())
    cout << "found hello" << endl;

if (s.count("world"))
    cout << "found world" << endl;
Map删除

Map删除主要有两种:

  • erase(key):按键删除
  • erase(it):按迭代器位置删除
c++ 复制代码
s.erase("hello");              // 按键删除

auto it = s.find("world");
if (it != s.end())
    s.erase(it);               // 按迭代器删除

Stack

Stack栈,即先进后出的一种数据结构,我们只能操作栈顶元素,因此基本的操作就涉及到入栈、出栈、取栈顶元素、获取栈长度。

创建栈

创建栈直接使用 stack<int> s即可创建栈s:

cpp 复制代码
#include<stack>
stack<int> s;
Stack入栈(push)

入栈就是把元素压到栈顶,使用 s.push(data)

cpp 复制代码
stack<int> s;
s.push(10);
s.push(20);
// 此时栈顶是20
Stack出栈(pop)

出栈就是删除栈顶元素,使用 s.pop()

cpp 复制代码
stack<int> s;
s.push(10);
s.push(20);
s.pop();            // 删除20
// 此时栈顶变成10

注意:pop() 只删除,不返回被删除的值

Stack取栈顶元素(top)

获取栈顶元素使用 s.top()

cpp 复制代码
stack<int> s;
s.push(10);
s.push(20);
cout << s.top() << endl;   // 20
Stack长度和判空

和前面的容器一样,长度和判空也是高频:

  • s.size():返回栈中元素个数
  • s.empty():判断栈是否为空
cpp 复制代码
stack<int> s;
cout << s.empty() << endl;   // 1(true)
s.push(1);
cout << s.size() << endl;    // 1
Stack常见坑
  • top()pop() 之前一定先判断 empty(),空栈调用会出错
  • pop() 没有返回值,如果你要拿到栈顶值,要先 top()pop()
  • stack 不支持下标访问,也不支持普通迭代器遍历
cpp 复制代码
if (!s.empty()) {
    int x = s.top(); // 先取值
    s.pop();         // 再删除
}

Queue

Queue队列就是先进先出的FIFO,我们队列是可以对队首和队尾进行操作的,也是入队,出队,访问队首/队尾,队列长度。

创建队列

创建队列直接使用 queue<int> q 即可:

cpp 复制代码
#include <queue>
using namespace std;

queue<int> q;
Queue入队(push)

入队就是在队尾添加元素,使用 q.push(data)

cpp 复制代码
queue<int> q;
q.push(10);
q.push(20);
// 此时队首是10,队尾是20
Queue出队(pop)

出队就是删除队首元素,使用 q.pop()

cpp 复制代码
queue<int> q;
q.push(10);
q.push(20);
q.pop();            // 删除队首10
// 此时队首变成20

注意:pop() 只删除,不返回被删除的值

Queue访问队首和队尾
  • q.front():访问队首元素
  • q.back():访问队尾元素
cpp 复制代码
queue<int> q;
q.push(10);
q.push(20);

cout << q.front() << endl;    // 10
cout << q.back() << endl;     // 20
Queue长度和判空

和其他容器一样:

  • q.size():返回队列元素个数
  • q.empty():判断队列是否为空
cpp 复制代码
queue<int> q;
cout << q.empty() << endl;    // 1(true)
q.push(1);
cout << q.size() << endl;     // 1
Queue常见坑
  • front() / back() / pop() 之前一定先判断 empty()
  • queuestack 一样,不支持下标访问,也不支持普通迭代器遍历
  • pop() 没有返回值,要先 front() 取值再 pop()
cpp 复制代码
if (!q.empty()) {
    int x = q.front(); // 先取队首
    q.pop();           // 再出队
}

unordered_map 和 unordered_set

这两个可以理解为无序版的Map和Set(底层是哈希表):

  • unordered_map:无序键值对
  • unordered_set:无序集合(元素唯一)

它们和 map/set 最大区别就是:不保证有序 ,但平均查找/插入/删除更快(平均 O(1))。会节省排序的时间

创建
cpp 复制代码
#include <unordered_map>
#include <unordered_set>
using namespace std;

unordered_map<string, int> mp;
unordered_set<int> st;
常用操作

其实和普通排序的没有太大区别

cpp 复制代码
mp["apple"]++;          // 计数,若不存在会先创建为0再+1
mp["banana"] = 3;       // 赋值
if (mp.find("apple") != mp.end()) { /* 找到了 */ }

st.insert(10);          // 插入
st.erase(10);           // 删除
if (st.count(10)) { /* 存在 */ }   // count返回0或1

C++进阶

下面再学习一下C++中常用的一些进阶的东西,方便快速刷题。

Bitset

Bitset位运算,需要知道初始化、一些常用的操作等。

Bitset创建

bitset 可以理解为"定长二进制数组",长度在编译期就确定:

  • bitset<8> b1;:创建8位全0
  • bitset<8> b2(13);:用整数初始化(13 的二进制)
  • bitset<8> b3(string("10101010"));:用二进制字符串初始化
  • bitset<9> b4(s, pos, n);:从字符串 spos 开始取 n 个字符初始化,包含pos位置
cpp 复制代码
#include <bitset>
using namespace std;

bitset<8> b1;              // 00000000
bitset<8> b2(13);          // 00001101
bitset<8> b3(string("10101010"));

string s = "1110010110";
bitset<9> b4(s, 1, 9);     // 取子串 "110010110"
Bitset常用操作
  • count():统计当前有多少个 1
  • size():返回总位数
  • set(pos):把第 pos 位设为 1
  • reset(pos):把第 pos 位设为 0
  • flip(pos):把第 pos 位取反
  • test(pos):判断第 pos 位是否为 1
  • any():是否至少有一个 1
  • none():是否全是 0
  • all():是否全是 1
cpp 复制代码
bitset<8> b(13);           // 00001101

cout << b.count() << endl; // 1的个数:3
cout << b.size() << endl;  // 位数:8

cout << b[0] << endl;      // 第0位(最低位)
b.set(1);                  // 第1位置1
b.reset(2);                // 第2位置0
b.flip(0);                 // 第0位取反

cout << b.test(3) << endl; // 第3位是否为1
cout << b.any() << endl;   // 是否至少有一个1
cout << b.none() << endl;  // 是否全是0
cout << b.all() << endl;   // 是否全是1
  • bitset<N>N 必须是编译期常量,不能是运行时变量
  • to_ulong() / to_ullong() 在值过大时可能抛异常
cpp 复制代码
bitset<8> b(string("00101100"));
cout << b.to_string() << endl; // 转字符串

只需要特别注意的是:该bitset的填充是从低位到高位(从右到左) ,即下标 b[0] 是最低位(最右边),不是最高位。


Sort函数

Sort函数就是排序函数,主要就是对数组(int a[10]和vector)进行排序,其中vector是容器,需要使用 vector.begin()vector.end() ;对于数组就是使用首地址即可。

使用方法
  • sort(first, last):默认按升序 排序,区间是 [first, last)左闭右开)。
  • 数组:sort(a, a + n)
  • vector:sort(v.begin(), v.end())
cpp 复制代码
#include <algorithm>
#include <vector>
using namespace std;

int a[5] = {4, 2, 5, 1, 3};
sort(a, a + 5);                 // 数组升序:1 2 3 4 5

vector<int> v = {4, 2, 5, 1, 3};
sort(v.begin(), v.end());       // vector升序:1 2 3 4 5

降序排序

  • sort(first, last, greater<int>()):按降序排序
cpp 复制代码
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;

vector<int> v = {4, 2, 5, 1, 3};
sort(v.begin(), v.end(), greater<int>()); // 5 4 3 2 1

自定义排序规则(常用)

  • 第三个参数可以传比较函数(或lambda),返回 true 表示"a应排在b前面"。

  • 或者可以理解成为 a>b 时为降序排列 (这样想:如果是假的话,前后则会交换位置)

  • a<b 时则为升序排列

cpp 复制代码
#include <iostream>
#include <algorithm>

using namespace std;

typedef struct {
    string name;
    int score;
} Student;

bool cmp(Student a, Student b) {
    if(a.score != b.score) {
        return a.score > b.score; // 分数不同,按分数降序排序
    } else {
        return a.name < b.name; // 分数相同,按名字排序
    }
}

int main()
{
    Student a[5];
    for(int i = 0; i < 5; i++) {
        cin >> a[i].name >> a[i].score;
    }
    sort(a, a + 5, cmp);
    cout<<"排序后:"<<endl;
    for(int i = 0; i < 5; i++) {
        cout << a[i].name << " " << a[i].score << endl;
    }
    return 0;
}
Sort常见坑
  • 一定记住区间是 [first, last),数组最后一个位置要写 a + n
  • sort(a, a + n - 1) 会漏掉最后一个元素
  • 比较函数必须满足"严格弱序",常见写法就是 return a < b;
  • sort 复杂度通常是 O(n log n),刷题足够快

cctype

这个在C语言中也是有的就是Ctype文件,它主要的作用就是对于类型进行判断以及对于字母大小写转换。

类型判断

<cctype> 里最常用的类型判断函数有这些:

  • isdigit(ch):是否是数字字符(0~9
  • isalpha(ch):是否是字母字符(a~z / A~Z)
  • isalnum(ch):是否是字母或数字
  • isspace(ch):是否是空白字符(空格、\n\t 等)
  • islower(ch):是否是小写字母
  • isupper(ch):是否是大写字母
  • ispunct(ch):是否是标点符号
cpp 复制代码
#include <iostream>
#include <cctype>
using namespace std;

char ch = 'A';
cout << isalpha(ch) << endl;   // 1
cout << isdigit(ch) << endl;   // 0
cout << isupper(ch) << endl;   // 1
大小写转换

<cctype> 里还可以直接做大小写转换:

  • tolower(ch):转小写
  • toupper(ch):转大写
cpp 复制代码
#include <iostream>
#include <cctype>
using namespace std;

char a = 'B';
char b = 'm';
cout << (char)tolower(a) << endl;   // b
cout << (char)toupper(b) << endl;   // M

字符串里也很常用:

cpp 复制代码
#include <string>
#include <cctype>
using namespace std;

string s = "HeLLo123";
for (char &ch : s) {
    ch = (char)tolower(ch);
}
// s = "hello123"

C++11特性

auto声明

auto 的核心作用就是自动类型推导,刷题里最常见就是:

  • 自动变量声明
  • 迭代器声明
自动变量声明
  • auto x = expr;:根据右值 expr 自动推导类型
  • 使用时必须初始化,否则编译器不知道类型
cpp 复制代码
auto a = 10;         // int
auto b = 3.14;       // double
auto c = 'A';        // char
auto s = string("hi"); // string
auto用于迭代器

这个是刷题里最常见的用途,能省掉很长的迭代器类型:

cpp 复制代码
vector<int> v = {1, 2, 3};
for (auto it = v.begin(); it != v.end(); ++it) {
    cout << *it << " ";
}
auto与引用(重要)

auto 默认会拷贝值,如果你想修改原对象,要加引用 &

cpp 复制代码
vector<int> v = {1, 2, 3};

for (auto x : v) {   // x是拷贝
    x *= 2;
}
// v 还是 1 2 3

for (auto &x : v) {  // x是引用
    x *= 2;
}
// v 变成 2 4 6

如果不想改值但又不想拷贝,可以用 const auto&

需要注意的是:

  • 使用拷贝方法就是不会改变原本的值(可以类比于局部变量)

  • 使用引用的话就可以直接改变值 (引用就是取别名)

auto常见坑
  • auto x; 这样写不行,必须初始化
  • auto 推导出来的类型有时不是你想的(尤其是引用/const会被去掉)
  • 看不准类型时可以先 cout << typeid(x).name(); 调试,或者显式写类型

to_string、stoi、stod

这三个函数是刷题里最常见的数字和字符串互转 工具,头文件都在 <string>

cpp 复制代码
#include <string>
using namespace std;
to_string(数字转字符串)
  • to_string(x):把数字转成字符串
cpp 复制代码
int a = 123;
double b = 3.14;

string s1 = to_string(a);   // "123"
string s2 = to_string(b);   // "3.140000"(默认会有6位小数)
stoi(字符串转int)
  • stoi(str):把字符串转成 int
cpp 复制代码
string s = "456";
int x = stoi(s);            // 456
stod(字符串转double)
  • stod(str):把字符串转成 double
cpp 复制代码
string s = "2.718";
double d = stod(s);         // 2.718

后续更多的C++的内容,就继续边刷力扣边学习提高一下效率了。

相关推荐
深邃-2 小时前
字符函数和字符串函数(1)
c语言·开发语言·数据结构·c++·算法·html5
初中就开始混世的大魔王2 小时前
3.1 DDS 层-Core
开发语言·c++·网络协议·tcp/ip·信息与通信
我真不是小鱼2 小时前
cpp刷题打卡记录24——路径总和 & 路径总和II
数据结构·c++·算法·leetcode
nianniannnn2 小时前
力扣 347. 前 K 个高频元素
c++·算法·leetcode
曹牧2 小时前
JDK 1.6 ,无法通过安全套接字层(SSL/TLS)加密建立数据库安全连接
java·开发语言·ssl
漫随流水2 小时前
c++编程:求阶乘和
数据结构·c++·算法
再卷也是菜2 小时前
算法基础篇(13)单调栈
数据结构·c++
星夜夏空993 小时前
搭建内存池2.0
c语言
云栖梦泽3 小时前
Linux内核与驱动:2.驱动基础(编译驱动)
linux·服务器·c++