细数 C++ 那些比起 C语言 更爽的特性

结构体定义

C:

typedef struct Vertex {
	int x, y, z;
} Vertex;
Vertex v1 = { 0 };

// or

struct Vertex {
	int x, y, z;
};
struct Vertex v1 = { 0 };

C++:

struct Vertex {
	int x, y, z;
};
Vertex v1 = {};

如果你一开始学的C++,再去写C的时候,你就会一脸懵逼怎么我的结构体编译不了。。。

为特定类型分配堆内存

C:

Vertex* ptr = malloc(sizeof(Vertex) * 10);
free(ptr);

C++:

Vertex* ptr = new Vertex[10];
delete[] ptr;

malloc 的参数是字节,所以得配合 sizeof 用。C++ 的 new 参数是个数,自动根据类型分配对应字节,看起来可读性更强。malloc始终返回的是 void*, C 里面 void* 可以任意转换到其他类型的指针。C++ 的 new 返回的是指定类型的指针,类型系统进更加严格。

计算固定大小数组的元素个数

C:

Vertex arr[1024];
int arrSize = sizeof(arr) / sizeof(Vertex);

C++:

Vertex arr[1024];
int arrSize = std::size(arr);

你当然可以写死 int arrSize = 1024; 但这样就不优雅了,不爽了。

RAII

C 语言经常出现 alloc、free 这样用来创建销毁资源的成对函数,新手很容易忘记调用 free 导致内存泄漏:

Ball* ball = ball_alloc();
// ...

while (ball->isLive) {
// ...
	if (ball->size > 5) {
		return; // 哦豁,完蛋
	}
}

ball_free(ball);

return;

特别是各种条件判断里面带 return 的,可能有人觉得在条件里面写 return 那是你代码风格有问题,这个就见仁见智了。

C++ 只要你写好析构函数,那以上问题你就不需要操心:

class Ball {
public:
	Ball ();
	~Ball ();
}

void foo () {
	Ball ball();
	// ...

	while (ball.isLive) {
	// ...
		if (ball.size > 5) {
			return;
		}
	}

	return;
} // 退出 foo 函数之前必定会执行 ~Ball

准确来说,C++ 变量结束生命周期的时候,就会执行它对应的析构函数,再具体一点,就是当你离开一个大括号的范围时,在这个大括号里面创建的变量,都会析构,比如 for while 循环里面创建的变量,或者是 if 语句块里面创建的变量都是这样的,或者干脆你自己在中间写一个大括号:

int main () {
	{
		Ball ball;
		printf("");
	} // 这里 ball 会析构

	return 0;
}

可惜 C++ 不能从语句块返回一个值,rust 就有这个不错的特性。

引用

引用用的好,指针不需要,当你用引用可以解决问题的时候,就别用指针。引用不存在野指针这类情况,他的作用范围更加严格。对引用操作,就是对本体操作,也不需要和指针一样用 ->,直接 . 就好。指针类型的变量需要内存空间来存储一个内存地址,而引用只是一个别名,不需要空间存储内存地址。对于 a.b.c.d 这样一长串的表达式,用引用会更舒服(auto& d = a.b.c.d)。

rust语言里面变量所有权概念,就是对C++引用拓展而已。

动态数组 vector

前面说了 C++ 的 new 是个好东西,但是 vector 更好。vector 本身有析构函数,生命周期结束自动调用里面每一个对象的析构函数,所以不用像 new 一样需要 delete。通常 C语言函数 传入一个数组,一般需要同时传入数组指针和数组大小,但是 C++ 你可以直接把 vector 当参数传入,本身就可以调用 size() 获取大小。

C:

void foo (Vertex* arr, int size) {
// ...
}

C++:

void foo (vector<Vertex>& arr) {
// ...
}

C++ 可以*选择传引用还是传值,C语言只能传指针。即便你在参数写上 Vertex arr[10],你以为他就能传值了?错了,当你想用 sizeof (arr) 得到数组大小时,它返回的是指针的大小,所以这就说明传进来的还是指针。

同样的道理,当你想返回数组,在函数返回类型写上 Vertex[10] 的时候,也是不行的,没有这样的写法,即便是固定大小的数组都不行。所以很多 C API 需要返回数组的时候怎么办?答案就是,你先自己分配好内存,再把指针传进去,他写入内容。那如果你也不知道数组长度多少怎么办,那一般会有一个API负责可以返回大小。

C++ 就爽快多了,你直接返回你在函数里面创建的 vector 就行,编译器会很贴心把这个变量的生命周期转移给调用者,不会发生任何额外复制。

C:

{
	int size = GetSize();
	Ball* balls = malloc(sizeof(Ball) * size);
	GetBalls(balls, size);
	free(balls);
}

C++:

{
	vector<Ball> balls = GetBalls();
	// 爽爽爽
}

对了,vector<bool> 请谨慎使用

上一篇:图算法 广度优先和深度优先递归与非递归


下一篇:《面试》-- 简单使用Python解决图结构的最小路径 -- Dijkstra算法