C转C++教程

Connor

Why C++

经过大一一整年的学习,相信各位的C语言都有了扎实的基础——。
但是咱们课程不讲C++,但是咱的算法题很多用C++会变得更方便,所以还得会。
于是笨人最近在学习C++,故写进博客。


先来看一段简单的代码

1
2
3
4
5
6
7
8
9
10
#include<iostream>

using namespace std;

int main(){
int n;
cin >> n;
cout << "Hello World " << n << endl;
return 0;
}

嗯,很显然这是一个输入n,然后输出Hello World再输出n的程序

我们先来看第1行
#include<iostream>
其实就是跟C语言一样导入库,只不过导入的不是<stdio.h>库了,<iostream>可以认为是一个输入输出流的库。

接下来再康第三行

1
using namespace std;

的意思是 使用 命名空间 std。

这地方要说可能要说挺多。结论是:现在做算法题和小程序建议加上这一句,解释不想看的可以快进(嗯嗯。

下面是解释

命名空间?

命名空间是什么

在C++中,命名空间(namespace)是一种用来防止名字冲突的机制。
如:

1
int count = 10;

如果写了很多个库,每个库里都有一个count变量,那编译器就不知道该用哪个了。

于是为了解决这种问题,C++把不同库里的名字放进不同的命名空间里。

1
2
3
4
5
6
namespace A{
int count = 10;
}
namespace B{
int count = 10;
}

通过这样,就可以在使用时,区分两个count,如

1
2
A::count += 1;
B::count += 2;

就把两个命名空间里的count区分开了。

std命名空间

C++ 标准库里的所有东西(比如 cout, cin, string, vector 等等)
都被放进了一个叫 std(standard) 的命名空间里。

所以严格写法是

1
2
3
4
5
6
7
8
#include<iostream>

int main(){
int n;
std::cin >> n;
std::cout <<"Hello World " << n << std::endl;
return 0;
}

这里的std:: 表示从命名空间std里找cin,cout,endl

using namespace std;的作用

这条语句的意思是:从现在开始,使用命名空间std里的所有名字时,不需要加前缀std::
起到了一个省略的作用
于是就变成了最上面的那个代码的样子

有时不建议使用它

using namespace std的写法,在小程序里非常方便。
但如果在大型项目和库开发中,就可能引发命名冲突。

如自己写了一个

1
void sort(){/* ... */}

但标准库里也有一个std::sort()
所以,如果用了using namespace std;,编译器就可能混淆这两个函数。

所以最安全的做法就是不用这个语句,干脆把所有的std::前缀都加上。


cin, cout

就是C++的输入和输出函数,在一定条件下可以代替scanf,printf函数(不过效率赶不上)。
使用cin的时候就不用加取地址符&

注意两个函数使用时的箭头方向>> <<
若有多个输入和输出的元素,可以用多个箭头链接,如

1
2
cin >> a >> b >> c;
cout << a << ' ' << b << ' ' << c << endl

endl

end of line的简写,用来代替换行。
当然也可以用\n替代

bool

再来看一个简单的代码

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;

int main(){
bool flag1 = true;
bool flag2 = -1;
bool flag3 = 0;

cout << flag1 << ' ' << flag2 << ' ' << flag3 << endl;
return 0;
}

输出的结果为
1 1 0
bool只有两个取值,trueflase
任何非0的值,都会被转成true,只有0会被转成flase。

提示:bool虽然只有0和1两个值,可以用二进制的1位表示,但是bool变量实际是占1个字节(8位)。
为什么?

因为:
计算机的最小可寻址单位是字节(byte)。
CPU 不能直接寻址“半个字节”或“1 bit”的内存单元,
所以即使只有一个布尔值,也得分配整整 1 个字节。

string类

字符串的定义

1
2
3
string s1 = "hello";
string s2 = "world";
string s3;

so easy~

字符串的更改

和C语言一样,可以通过使用

1
s[0] = 'H';

这种方式更改

字符串的拼接

1
s3 = s1 + s2;

直接用’+’就可以链接字符串了,so easy~。

字符串的输入

这里可以用C语言类比一下。
使用

1
cin >> s

就可以把输入字符串了,其中cin会在读取到空格、回车时停止,和scanf相同。
那当然还有输入一行的了~

1
getline(cin, s);

这里的cin是指从标准输入流(键盘)中读入字符串,存到字符串s里。

字符串的输出

1
cout << s;

so easy~

字符串的长度

所求字符串s的长度,这个跟C语言有些不同
使用

1
int len = s.length();

即可把字符串s的长度存到len
为什么要这么写而不是像strlen(s)一样,把字符串放到函数里呢?

让我们回看这一节的标题,字符串类,没错他是一个类。那么学过java的都知道了,类是有属性和方法的,这里的length()就是一个方法,返回这个字符串的长度。

字符串的复制

可以直接用 = 来赋值,如

1
2
s1 = 'hello world';
s2 = s1;

字符串其他常用函数

s.empty()
判断s是否为空,空返回1,否则返回0。

s.front()
返回s第一个字符

s.back()
返回s最后一个字符

s += t / s.append(t)
拼接字符串

s.push_back(c)
添加单个字符到末尾

s.pop_back()
删除末尾字符

s.clear()
清空字符串

s.find(t)
在字符串s里找字符串t首次出现位置

s.substr(pos, len)
获取s中pos处,长度为len的字符串

s.substr(pos)
如果只输入一个值的话,就是获取从pos处到字符串s末尾的子串

s.compare(t)
把s和t比较,等同于strcmp(s, t)等同于

s1 == s2
判断两个字符串是否相等,若相等返回true,否则false。

不常用但有时会用的函数

s.insert(pos, t)
在指定位置插入字符串

s.erase(pos, len)
删除子串

s.replace(pos, len, t)
替换子串

s.swap(t)
交换两个字符串

结构体

C++的结构体使用和C有些不同

看这个例子

1
2
3
4
struct stu{
string name;
int age;
};

比如我们要生成一个stu的数组,在C中我们需要写

1
struct stu a[10];

而在C++中,我们只需要写

1
stu a[10];

看出不同来了吧~,也就是说在这里我们不需要使用typedef了~

&的使用

看下面这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using namespace std;

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

int main(){
int a = 2;

plus1(a);

cout << a << endl;

return 0;
}

通过这种方法,可以直接实现a += 1,即输出3

int &a 表示 引用,也就是,别名
当你传入 a 时,函数内部的 a ,实际上就是 main 中的 a
对它的修改会直接影响原变量

也就是直接把形参变成了实参~。


vector

vector是啥?向量?彳亍。更精确的说,是可变数组——
(以下简称数组)

创建数组、分配数组大小

让我们先来看一段代码~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
#include<vector>

using namespace std;

int main(){

vector <int> v;
cout << v.size() << endl;

v.resize(10);
cout << v.size() << endl;

return 0;
}

这啥东西啊我怎么忽然就看不懂了
这段代码的输出是

1
2
0
10

先看代码的第2行,使用vector之前,需要先导入vector库,即需要#include<vector>

how to创建数组??

我们只需要通过第8行中vector <int> v就可以创建一个int类型的数组了!
那这个数组的大小的是多大呢,我们可以通过调用v.size()并且输出看一下,这个方法会返回数组v的大小。

于是乎我们看到了,输出结果第一行是0,也就是说咱创建的数组长度是0。

长度是0像话吗。咱们肯定需要更改一下这个数组的长度,这时只要通过使用
v.resize(length)
既可以重新分配数组的大小了,再输出,我们就看到原数组的大小改变了。

创建数组还有三种方式,分别是

1
2
3
vector <int> v(10, 2);
vector <int> u(10);
vector <int> v = {1, 2, 3, 4, 5, 6};

这两种方式与第一种方式的参数数量不同。
vector <int> v(10, 2);作用就是创建一个长度为10的数组v,并且初始化数组中每个元素都是2。
也就是说省去了resize的过程,并且顺便初始化了整个数组

vector <int> u(10),其实等价于vector <int> u(10)
即把数组u中每个元素都初始化成0。

至于vector<int> v = {1, 2, 3, 4, 5, 6},也就是和C语言一样手动初始化数组

另外 v.resize(n, val)实际也有两个参数
当缺少参数val时,只更改数组大小(如果之前小,新的就补0,之前大就删掉
当加上参数val时,则新加的元素会自动初始化成val,但是如果之前的长度比更改后的长度n长,那么参数val就会被忽略。

更改数组元素

这里用法与C语言相同,如使用v[0] = 10就可以直接更改了。

末尾添加新的元素

通过使用

1
v.push_back(data);

就可以在v后追加元素
例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
#include<vector>

using namespace std;

int main(){

vector <int> v(10, 2);

v.push_back(11);

for(int i = 0; i < 11; i++){
cout << v[i] << ' ';
}

return 0;
}

输出的结果是

1
0 0 0 0 0 0 0 0 0 0 11 

我们发现,前十个初始化成0的元素并没有被改变,而是多出了第11个元素。
也就是说,pushback(data)会把data这个值放在原来的数组后面,也就是更改了数组长度。
此时再使用

1
cout << v.size();

会发现数组的长度变成了11。

迭代器

我们看一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
#include<vector>

using namespace std;

int main(){

vector <int> v(6, 2);
v.push_back(11);
v.push_back(12);

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

return 0;
}

输出的结果是

1
2 2 2 2 2 2 11 12 

看到这个for循环和输出我们就猜到了,这个迭代器的作用说白了就是遍历。

auto p = v.begin(),也就是让迭代器p指向v的第一个元素。
为什么说指向?也就是说vector这里的p就是一个指针。
略微不同之处是,我们不用关心它的类型,即使用auto p就可以创建迭代器了。

p != v.end() 也就是p还不等于的end处。注意,这里的p.end()不是最后一个元素喔,是最后一个元素的==下一个元素==。

也就是说!

1
2
2 2 2 2 2 2 11 12 
^ p.end()在这里!

这也保证了v.size() = v.end() - v.begin();

一些底层逻辑

不关心的可以跳过(

<vector>的底层逻辑,其实就是数组,占用一块连续的内存,其内部维护了三块指针,分别是

1
2
3
4
T* _begin;   // 指向数据开头
T* _end; // 指向最后一个有效元素的后面
T* _capEnd; // 指向已分配空间的末尾

对应示意图

1
2
| 元素1 | 元素2 | 元素3 | 空 | 空 | 空 |
^begin ^end ^capEnd

这样有

  • size() = _end - _begin
  • capacity() = _capEnd - begin

这里出现了一个新的函数,v.capacity(),表示vector最多能容纳多少个元素,而不用再次分配内存。也就是说,v.size()只是数组的长度,而不是内存的长度。而真正的内存长度会由_begin_capEnd两个指针共同决定。

初始化数组和使用resize()更改数组长度时,会根据长度自动分配数组占用内存。此时sizecapacity相等。

当使用v.push_back(num)后,会检测当前数组占用的内存是否都被利用了,如果没有,即end!=capEnd,此时不需要更改内存大小,直接后移end指针即可。

但如果end == capEnd,此时内存不够,就需要扩容了。
但我们知道数组占用的内存是连续的,扩容的话必须再找另外一块更大的连续的内存了。
所以此时,会重新分配一块更大的空间,并且把原来的数据全部拷贝过去,再释放旧内存。

但是为了避免多次v.push_back()扩容反复拷贝浪费时间,这里采用指数级增长,即让新的capacity是原来的两倍,降低拷贝次数,节省时间。

而为了减少频繁扩容带来的开销,可以手动提前“预留空间”
v.resize()类似,即使用
v.reserve(1000); 就可以手动更改预留内存空间。

set

set是什么?集合!

创建和插入

set中的元素有两个特性

  • set中的元素是互异的。
  • set中的元素会自动排好序
    来看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
#include<set>

using namespace std;

int main(){
set <int> s;
s.insert(1);
s.insert(3);
s.insert(2);
s.insert(1);

for(auto p = s.begin(); p != s.end(); p++){
cout << *p << ' ';
}
return 0;
}

第二行,使用set前,要先导入set库,即#include<set>

set创建和vector类似,通过set <type> set_name方式进行创建,如代码的第七行。
有一点不同是,set_name后是不带lengthval参数的。

set <int> s(10);
这种方式不能通过编译。

也很好理解,集合这个东西,应该是要随时加元素和删元素的,为什么要分配空间呢。

但是如果想创建同时初始化集合,使用
set <int> s = {1, 2, 3};
这种方式是阔以的。

想在集合中插入元素,只需要通过
s.insert(val);
就可以~

在上面的代码中,我们先后试图插入了1, 3, 2, 1四个元素,随后通过迭代器输出集合s中的元素。
这段代码运行的结果是
1 2 3
没戳,正如这一节一开始说的那样,集合是有互异性的,并且set中的元素是会被自动排序(默认升序)的。

那怎么让它降序呢?只需在创建set时这么写~
set <int, greater<int>> s
s就会定义成一个默认降序的集合了~

集合大小

使用s.size()就可以~

元素查找

如何查找集合中的元素呢,只需使用
s.find(val)
若集合s中存在val这个元素,会返回val这个元素的==位置==
但如果不存在,会返回s.end()这个位置!而不是NULL

1
2
3
set <int> s = {1, 2, 3};
cout << (s.find(2) == s.end()) << endl;
cout << (s.find(4) == s.end()) << endl;

输出的结果是

1
2
0
1

元素删除

只需要通过
s.erase(val)就可以把集合中val这个元素删掉了~
如果s中没有val这个元素,这行运行之后将无事发生。


map

map是什么?键值对!也就是python里的字典——
什么是键值对?也就是一个值映射一个值,其实也就是哈希表

创建和插入

我们来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
#include<map>

using namespace std;

int main(){
map <string, int> m;
m["hello"] = 114514;
m["world"] = 666666;

cout << "hello : " << m["hello"] << endl;
cout << "world : " << m["world"] << endl;
return 0;

}

使用map前,要先导入map
创建map时,只需使用
map <key, val> m就可以创建一个map了,这里的keyval都不限制类型。
这里的key就是“键”,val就是“值”。

如何添加一个键值对?
只需要使用类似上面代码8,9行的代码。使用m[key] = val进行赋值操作
map中不存在那个键是key的键值对,就会创建<key, val>这个键值对
如果已经存在一个键就是key的键值对<key, val>了,那么赋值操作就会更改那个val

访问

如何访问键值对?只需要使用
m[key]就会返回<key, val>键值对中val的值了
上面那段代码的输出是

1
2
hello : 114514
world : 666666

那如果不存在这个键值对呢,程序会自动插入一个键值对<key, val>,并且把val是默认初始化的值(如int初始化成0)
比如这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
#include<map>

using namespace std;
int main(){
map <string, int> m;
m["hello"] = 114514;
m["world"] = 666666;

cout << "hello : " << m["hello"] << endl;
cout << "world : " << m["world"] << endl;
cout << "nihao : " << m["nihao"] << endl;
return 0;

}

的输出将是

1
2
3
hello : 114514
world : 666666
nihao : 0

查找

有时,我们只是想查找或者访问已经存在的键值对,而不想在访问键值对的同时就把不存在的键值对也加进去。对此,我们只要调用
m.find(key);就可以~
比如

1
2
3
if(m.find("hello") != m.end()){
cout << "hello : "<< m["hello"];
}

再次提醒。STL里的find()函数,若查找失败,返回的都是end(),而不是空指针!

迭代器

在使用map的迭代器时,需要注意要通过

1
2
3
for(auto p = m.begin(); p != m.end(); p++){
cout << p -> first << ':' << p -> second << ' ' << ' ';
}

这种方式。用p -> first来访问“键”,用p -> second来访问“值”。
直接访问*p会编译错误

这里的用法和指针类似,很好理解~


unordered_set and unordered_map

其实就是不会在插入时自动排序的setmap
使用方法完全和setmap相同

由于不会自动排序,所以节约了时间~如果使用setmap时TLE,可以考虑使用这两个unordered_setunordered_map

不过无序,并不代表使用迭代器输出时,输出顺序就是插入顺序——绝大概率是乱序的!

一些底层逻辑

setmap的实现用的是红黑树(一种特殊的二叉查找树),保证了插入并排序、查找,和删除的时间复杂度都是O(logn),很快~

推荐一个B站上讲解红黑树的可视化视频

unordered_setunordered_map是用哈希表和链表实现的,所以更快,也能解释为什么输出顺序是乱序的~

stack用法

使用前先导入
#include<stack>

然后创建
stack <type> s;

然后一堆操作

压栈
s.push(i);
出栈
s.pop();
访问栈顶
s.top();
获取长度
s.size();
判断是否为空
s.empty();

想必从第一节看过来的不用解释都能看懂啦~

不能使用迭代器(栈怎么能遍历呢?)

queue用法

使用前先导入
#include<queue>

然后创建
queue <type> q;

然后一堆操作~

入队
q.push(i);
出队
q.pop();
访问队首
q.front();
访问队尾
q.back();
获取长度
q.size();
判断是否为空
q.empty();

想必从第一节看过来的不用解释都能看懂啦~

不能使用迭代器(队列怎么能遍历呢?)

priority_queue用法

优先队列!堆!!

使用前先导入
#include<queue>

然后创建
priority_queue <type> pq;
(默认是一个大根堆)

如果想要一个小根堆呢?
只需要这么写
priority_queue<int, vector<int>, greater<int>> pq;
==注意,<>里的三个参数缺一不可==

一些操作

插入堆
pq.push(i);
删除堆顶元素
pq.pop();
访问堆顶元素
pq.top();
判断是否为空
pq.empty();
返回元素数量
pq.size();

想必从第一节看过来的不用解释都能看懂啦~

不过不能直接堆排序~,想输出排序结果的话,可以使用

1
2
3
4
while(!pq.empty()){
cout << pq.top() << ' ';
pq.pop();
}

就可以啦~

不能使用迭代器(堆怎么能遍历呢)


STL暂时告一段落啦~~~

  • Title: C转C++教程
  • Author: Connor
  • Created at : 2025-10-14 13:13:28
  • Updated at : 2026-06-30 23:53:47
  • Link: https://redefine.ohevan.com/2025/10/14/CtoC++/
  • License: This work is licensed under CC BY-NC-SA 4.0.