结构体定义

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> 请谨慎使用

auto 关键字

这个仅限于写的人爽,看的人应该会很痛苦。因为C++有了泛型(呃,或者我应该叫它模板类?),导致类型名字会变得很长,特别是模板类里面还有模板类的套娃情况,此时用 auto 就会十分爽了。更加惊喜的是,连函数返回类型都可以 auto。

auto GetBalls () {
vector<Ball> balls;
// ...
return balls;
} int main () {
auto balls = GetBalls();
}

不知道有没有开源项目全程 auto 的,我想观摩观摩。。。

std::string

C语言 表达字符串就是很简单的用 char* 表示, 最后一个 char 为 0,代表字符串结束,这很便利,所以 printf 等函数不需要你告诉他字符串的长度,他自己遇到 0 就停下来了。函数 strlen 也因此可以计算字符串长度。如果你是其他语言过来的,期待可以字符串可以用 + 号连接,那你要失望了,C语言没有这种操作,通常做法是用 sprintf,不仅写起来麻烦,还需要你自己先准备好一个“足够”长的缓冲区,每次一些函数告诉我需要一个缓冲区但不告诉我多长的时候,我就会生理不适。后期增加了一个新函数 sprintf_s ,需要明确告诉函数你的缓冲区有多长,这样可以避免写出界,但依然没有改变用起来很麻烦的情况。

C++ 有了一个新选择:std::string,他和 vector 非常相似,也支持很多类似的操作。最惊喜的是,它重载了 + 运算符,可以直接把 string 和 string,甚至 stringchar* 直接相加,得到一个新的 string:

string str = string("one") + "two" + "three";

printf(str.c_str());

c_str() 返回一个 const char* 来兼容 C API 的操作,但是千万注意这个指针的生命周期,当你拿着它到处传递的时候,务必注意 string str 什么时候会析构。

有的时候 sprintf 其实比+更有用,但 string 和 sprintf 一起用的时候,又回到了从前。。。也许 C++ 应该有个配套的字符串格式化函数吧。。。但不好意思,很长时间都没有这种东西,直到 C++20,才有了 std:format,起码过去了20年,20年!知道这20年大家怎么过的吗!

函数重载与默认参数

C++:

void foo (int a = 0, int b = 0);

void foo (int a, int b) {
// ...
} int main () {
foo(); // ok
foo(1); // ok
foo(1, 2); // ok
}

不多解释,反正 C语言 就是不行。

命名空间

C语言 你写的每一个函数其实都是全局的,都得给他取一个名字,当你把其他库链接进来的时候,这些名字可能会和其他库里面名称产生冲突,唯一的解决办法就是改名字。

C++ 的命名空间完美解决了此类问题,你可以起一个长一点的 namespace,然后使用短的函数名称,别人可以决定使用完整的名称,又或者声明省略整个空间名(其实是把指定空间合并到当前的命名空间),又或者给空间名取一个别名。

namespace giegie {
void xinteng() { }
} int main()
{
giegie::xinteng(); {
using namespace giegie;
xinteng();
} {
namespace gg = giegie;
gg::xinteng();
} return 0;
}

并且可以自由决定这种行为的作用域。

lambda 表达式

很多场景需要你传递一个函数指针,用于回调,C语言你就得在全局声明一个函数了,而 C++ 你可以直接在函数,甚至语句块内部使用 lambda 表达式,严格限制范围,增强代码可读性。lambda 在不使用捕获的情况下可以轻松自动转换为纯函数指针。lambda 的捕获不得不说实在是非常惊艳,可以像 Javascript 语言那样直接访问到 lambda 外部的变量:

int main() {
int a = 1;
int b = 2; // 这里要是没有 auto 我都不会写了
auto foo = [&a, &b](int c) {
return a + b + c;
}; int sum = foo(3); // sum is 6
}

你可以自由决定是把 a、b 复制传递,还是直接传引用。复制你就无需担心捕获变量的生命周期问题,适用于异步调用的情况。引用捕获你可以对外部变量直接修改。

结尾

以上说的这些爽快的特性,必须要你经历过C语言一段时间的洗礼后,才能深有体会。C++ 当然还有很多没说到到的新特性,我也只是挑一点来说而已,比如最重要的 class 我反而只字未提,很多人觉得必须要把 C++ 所有特性全部掌握,才算是会 C++,才有资格用,我认为大可不必,并不是语言提供了什么特性你都非得要用上,面向过程可以干净利落解决问题就没有必要非得面向对象。况且有些“特性”真的一言难尽,比如我就宁愿用 printf 而不是 cout。

细数 C++ 那些比起 C语言 更爽的特性的更多相关文章

  1. 迄今最安全的MySQL?细数5.7那些惊艳与鸡肋的新特性(上)【转载】

    转自: DBAplus社群 http://www.toutiao.com/m5762164771/ 迄今最安全的MySQL?细数5.7那些惊艳与鸡肋的新特性(上) - 今日头条(TouTiao.com ...

  2. 细数 Windows Phone 灭亡的七宗罪(过程很详细,评论很精彩,但主要还是因为太慢了,生态跟不上,太贪了,厂商不愿意推广)

    曾梦想仗剑走天涯,看一看世界的繁华 年少的心有些轻狂,如今你四海为家 曾让你心疼的姑娘,如今已悄然无踪影 犹记得上大学攒钱买了第一台智能手机Lumia 520时,下载的第一首歌曲<曾经的你> ...

  3. 细数iOS上的那些安全防护

    细数iOS上的那些安全防护  龙磊,黑雪,蒸米 @阿里巴巴移动安全 0x00 序 随着苹果对iOS系统多年的研发,iOS上的安全防护机制也是越来越多,越来越复杂.这对于刚接触iOS安全的研究人员来说非 ...

  4. 细数.NET 中那些ORM框架 —— 谈谈这些天的收获之一

    细数.NET 中那些ORM框架 —— 谈谈这些天的收获之一(转) ADO.NET Entity Framework        ADO.NET Entity Framework 是微软以 ADO.N ...

  5. 细数Qt开发的各种坑(欢迎围观)

    1:Qt的版本多到你数都数不清,多到你开始怀疑人生.从4.6开始到5.8,从MSVC编译器到MINGW编译器,从32位到64位,从Windows到Linux到MAC.MSVC版本还必须安装对应的VS2 ...

  6. 细数Python Flask微信公众号开发中遇到的那些坑

    最近两三个月的时间,断断续续边学边做完成了一个微信公众号页面的开发工作.这是一个快递系统,主要功能有用户管理.寄收件地址管理.用户下单,订单管理,订单查询及一些宣传页面等.本文主要细数下开发过程中遇到 ...

  7. Five things that make Go fast-渣渣翻译-让GO语言更快的5个原因

    原文地址:https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast 翻译放在每个小段下面 Anthony Starks has ...

  8. 细数MQ那些不得不说的8大好处

    消息队列(MQ)是目前系统架构中主流方式,在大型系统及大数据中广泛采用.对任何架构或应用来说, MQ都是一个至关重要的组件.今天我们就来细数MQ那些不得不说的好处. 好处一:解耦 在项目启动之初来预测 ...

  9. 细数Intellij Idea10个蛋疼问题!

    Intellij Idea以下简称IJ. 昨天细数了IJ上的10大666的姿势,IJ确实很智能,在很多方便可以完爆Eclipes,可在某些方面真的被Eclipse秒杀 1.乱码 在Eclipse中很少 ...

随机推荐

  1. Hive相关的命令

    hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的sql查询功能,可以将sql语句转换为MapReduce任务进行运行. 其优点是学习成本低,可以通过 ...

  2. 扩展欧几里得算法(EXGCD)学习笔记

    0.前言 相信大家对于欧几里得算法都已经很熟悉了.再学习数论的过程中,我们会用到扩展欧几里得算法(exgcd),大家一定也了解过.这是本蒟蒻在学习扩展欧几里得算法过程中的思考与探索过程. 1.Bézo ...

  3. Dotnet洋葱架构实践

    一个很清晰的架构实践,同时刨刨MySQL的坑.   一.洋葱架构简介 洋葱架构出来的其实有一点年头了.大约在2017年下半年,就有相关的说法了.不过,大量的文章在于理论性的讨论,而我们今天会用一个项目 ...

  4. 干掉前端!3分钟纯 Java 注解搭个管理系统

    大家好,我是小富~ 最近接触到个新项目,发现它用了一个比较有意思的框架,可以说实现了我刚入行时候的梦想,所以这里马不停蹄的和大家分享下. 在我刚开始工作接触的项目都还没做前后端分离,经常需要后端来维护 ...

  5. 面试题:ApplicationContext和BeanFactory两种容器区别

    ApplicationContext和BeanFactory两种容器区别 BeanFactory是ApplicationContext容器的父接口 BeanFactory(多例模式): BeanFac ...

  6. 【Spring Cloud & Alibaba全栈开源项目实战】:SpringBoot整合ELK实现分布式登录日志收集和统计

    一. 前言 其实早前就想计划出这篇文章,但是最近主要精力在完善微服务.系统权限设计.微信小程序和管理前端的功能,不过好在有群里小伙伴的一起帮忙反馈问题,基础版的功能已经差不多,也在此谢过,希望今后大家 ...

  7. 通过lms.samples熟悉lms微服务框架的使用

    经过一段时间的开发与测试,终于发布了Lms框架的第一个正式版本(1.0.0版本),并给出了lms框架的样例项目lms.samples.本文通过对lms.samples的介绍,简述如何通过lms框架快速 ...

  8. MyBatis笔记(三)

    1. ResultMap 查询结果为null:要解决属性和字段名不一致的问题 我们先来看下步骤: 数据库中的字段名 Java中的实体类 public class User {    private i ...

  9. 软工2021个人阅读作业#2——构建之法和CI/CD的运用

    项目 内容 这个作业属于哪个课程 2021学年春季软件工程(罗杰 任健) 这个作业的要求在哪里 2021年软工-热身阅读作业#2 我在这个课程的目标是 了解和掌握现代软件开发和项目管理技术,锻炼在大规 ...

  10. JavaScript深入理解-Set、Map、WeakSet和WeakMap

    Set Set 对象允许储存任何类型的唯一值,无论是原始值或者是对象引用 本质:构造函数,用来生成 Set 数据结构 描述 Set 对象是值的集合,你可以按照插入的顺序迭代它的元素.Set 中的元素只 ...