前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实用编程技巧汇总,让代码效率提高一个档次

实用编程技巧汇总,让代码效率提高一个档次

作者头像
老九君
发布2020-02-13 12:16:57
6500
发布2020-02-13 12:16:57
举报
文章被收录于专栏:老九学堂老九学堂

在编程过程中

有小伙伴说我敲代码又不好看还慢

怎么办?

今天大雄给大家介绍几个编程小技巧

让你的代码迅速提高档次

for循环

1

for循环变量初始化

在c语言中,我们常常这样使用for语句:

代码语言:javascript
复制
for (int i = 0; i < strlen(s); i++)

这看起来似乎很完美,代码也很漂亮,让我们再看看另一种写法:

代码语言:javascript
复制
for (int i = 0, len = strlen(s); i < len; i++)

二者唯一的不同在于后者用len变量将字符串s的长度保存了,在条件判断时直接将i与len比较。

第二种方法用一个额外变量len避免了每次条件判断都要重复执行函数strlen(s),而执行该函数是非常耗时的(假设字符串的长度为n,函数执行的复杂度为O(n)),尤其是当for循环体的语句比较少,字符串比较长的时候。

在很多leetcode题目中,两种不同的写法需要的运行时间相差巨大。

同样在C++、Java中,这种写法for (int i = 0; i < s.length(); i++),也是不值得推荐的。

尽管C++编译时期有的编译器会将length()函数用内联或者一个确定的变量来替代,Java也会将其用“属性”来替代,但很多小伙伴仍然倾向于使用后者。

有意思的是,在Python的语法中,for循环用这种方式来表示:

代码语言:javascript
复制
for i in range(len(s))

这就避免了重复去求字符串s的长度,这种方法既有语义感,又获得了高性能。

2

变量定义位置(for循环内部还是外部)

代码语言:javascript
复制
//内部
for (int i = 0; i < 10; i++)
{
 string s = ss[i];
 ...
}


//外部
string s;
for (int i = 0; i < 10; i++)
{
 s = ss[i];
 ...
}

如果定义在内部,每次循环都要重新定义string变量s,意味着每次循环都要调用构造和析构函数;而定义在外部每次循环只需要调用复制构造函数。

一般建议将大的对象定义到外部,提高运行效率,把小的对象定义在里面,提高程序可读性。

基本运算和函数

1

在乘以2(或2的整数次幂)或除以2(或2的整数次幂)的时候尽量用位运算来替代。

2

尽量减少使用除法运算(可以适当转换为乘法,如条件判断时将if (a == b / c)替换为if (a * c == b)。除法运算需要更多的移位和转换操作,往往需要的时间是相应乘法的两倍)

3

多使用+=、-=、*=、/=等复合运算符,以加一为例,效率由高到低是(i++ 、 i += 1 、 i = i + 1)

4

多掌握一些小巧的库函数,例如:swap, max, min, sort, qsort, ati, stoi...它们用起来方便,效率更是比一般人写的代码高。

inline、const、&修饰符

inline让函数内联,建议编译器将函数体代码“复制粘贴”到函数调用处,在函数体短小,函数调用又比较频繁的时候能有效避免因函数调用带来的内存开销(因为每一次调用函数系统都会生成许多额外的变量)。

const不仅仅可以保证其修饰的变量不被修改,提高程序的稳定性,同时也让编译器更好地为我们优化代码。

举个例子:我们如果用const修饰某一个常量,那么程序中所有用到该常量的地方都会用其值来代替,这样就避免了读取其地址而浪费时间。

&修饰返回值类型和参数类型表示采取引用的方式传递,避免了对象赋值构造所需的时间和内存。

缓存(cache)和寄存器(register)

除了CPU,就是寄存器和缓存的访问速度最高了,一般不建议我们自己定义寄存器变量和控制数据缓存,编译器会自动帮我们把经常用到的一些数据放到缓存和寄存器中。

但是,了解一些编译器控制数据的依据对编程也有极大帮助。

一般来说,放到寄存器/缓存的数据优先级为:用register修饰的变量,循环控制变量,auto局部变量,静态变量,用户自己分配的内存数据。

迭代器(iterator)

1

访问容器中元素的时候尽量使用迭代器而不是下标或者指针。

首先,迭代器访问元素类似与指针,相对于下标访问不用根据下标值计算地址,这在循环中能够节省不少时间。

其次,迭代器作为指针一种延拓,能更好的代表并操作其所指的对象,而在下标访问中我们往往用一个int值pos来表示pos下标下的元素,没有面向对象编程的直观。

再次,迭代器为我们访问各种容器(数组,vector,list,map,queue,deque,set …)中的元素提供了统一的方法,其作用类似于“语法糖”,让编程更加简单、方便。

2

另外在使用迭代器的自增和自减运算符需要注意,iterator++,和++iterator的效率有天壤之别。两种自增方式的运算符重载如下:

代码语言:javascript
复制
iterator & operator++()
{  // 前增
 ++*this;
 return (*this);
}

iterator operator++(int)
{  // 后增
 iterator temp = *this;
 ++*this;
 return (temp);
}
  • 后增(iterator++)相对于前增(++iterator)创建了一个临时迭代器temp,并将其返回,而前增直接返回原来迭代器的引用。
  • 在for循环中的频繁自增操作中,创建临时迭代器temp,以及返回temp时调用的复制构造函数所需的时间不容忽视。

vector容器

vector容器毫无疑问是C++STL使用最为频繁的容器了,当然这个强大容器的使用也包含了很多的小技巧。

1

在适当时候使用emplace和emplace_back函数来替代insert和push_back函数。

它们之间的区别很明显,insert和push_back函数参数是vector容器里面的模板对象,而带emplace的函数参数是模板对象的构造函数的参数。

这意味着后者将模板对象插入到vector容器的过程中不用先生成好对象,而是可以直接利用参数构造。

当然如果模板对象已经是生成好的,那就没有必要用emplace函数了。

在很多循环递归迭代中,往往需要反复向vector容器中添加对象,这时候额外构造一个对象所需要的时间和空间就不容忽视了,因此这是一个vector进阶用法的好trick。

2

vector容器的底层实现是数组,并且在当元素大于最大容量的时候会重新生成一个更大的数组,将原来数组中的对象复制构造到新数组中。

由于要重新分配大量内存以及反复调用复制构造函数,这对时间和空间的开销是巨大的。

为了减少内存的重新分配,我们可以适当的估计我们需要保存的元素数量,并在vector初始化的时候指定其capacity。

这种方法很直接但也有其缺点,就是我们往往很难在开始的时候就估计准确我们要保存的元素数量(如果能,我们就直接用数组得了)。

一个很好的解决办法是:将vector中保存的元素改为指针,指针指向我们真正想要保存的对象。

由于指针相对于其所指向的对象来说占用内存很小,而且在复制的时候不用调用复制构造函数,因此以上提到的一些缺点都能很好的克服。

事实上,对于能够熟练控制内存分配的老码农来说,这种vector + 指针的方式是十分完美的。

if条件判断

在进入讨论之前,我们先思考下面这个例子:

一个班的数学成绩如下:74、76、78、94、97、68、77、65、54、89…,总共有50个数据。

要求用程序将分数为优秀(>=80)、良好(>=70)、及格(>=60)、不及格(>=0)四个分数段。

代码语言:javascript
复制
for 所有学生分数
 if 分数 < 60
   归为不及格段
 else if 分数 < 70
   归为及格段
 else if 分数 < 80
   归为良好段
 else 
   归为优秀段

这个伪代码逻辑没有问题,但是就这个数据来看这段代码运行效率糟透了。

由于这个班的数学成绩绝大多数是良好和优秀,而这个程序需要三次if判断才能将分数归为良好,三次if判断加上一个else才能将分数归为优秀,所以绝大多数前两个if判断是不必要的。

我们将if判断语句的顺序变换下:

代码语言:javascript
复制
for 所有学生分数
 if 分数 >= 80
   归为优秀段
 else if 分数 >= 70
   归为良好段
 else if 分数 >= 60
   归为及格段
 else 
   归为不及格段

在这个伪代码中绝大多数分数都在前两个if语句中完成了分段。两者的时间效率相差巨大,实际运行也发现,前者是后者运行时间的两倍多。

switch分支判断

switch语句的底层实现主要有三种方式:转换为if else 语句,跳转表,树形结构。

当分支比较小时,编译器倾向于转换为if else语句,当分支比较多,分支范围很广时,用树形结构,当分支数量不算多,分支范围紧凑时,用跳转表。

跳转表的底层实现是数组映射,对条件转换的效率为O(1),相比于另外两种方式优势明显,因此我们应该尽量控制分支的数量,以及让各个分支的int型数据紧凑。

这些技巧小伙伴们都了解了吗?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老九学堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档