c++学习笔记(七)—— 坑点/技巧总结和其他tips

坑点/技巧总结

不要连续使用比较符

if (i < j < k)  // 若k大于1则为真

建议使用++i

原因:i++需要将原始值保留下来,会造成浪费。如果是迭代器类型,这种操作消耗就很大了

运算对象可按任意顺序求值

i = fa(x) + fb(y);  //fa和fb执行顺序不确定
*beg = toupper(*beg++); //错误:该赋值语句未定义

运算表达式中各项执行顺序不固定,上面第二个,编辑器有两种处理:
1、beg = toupper(beg); //先处理左边
2、(beg+1) = toupper(beg); //先处理右边

条件运算符优先级非常低

cout << grade < 60 ? 1 : 0;  //错误: 试图比较cout和60

模板类定义和实现不能分开

C++中每一个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的。只有模板被真正使用的时候,编译器才知道,模板套用的是什么类型,应该分配多少空间。这也就是模板类为什么只是称之为模板,而不是泛型的缘故。

悬垂else

无花括号的if-else,且if多于else时,else匹配最近的未被匹配的if

int a = 6;
int b = 6;
if (a > 5)
    if (b > 7)
        cout << "log1" << endl;
else
    cout << "log2" << endl;
// 最终输出log2

不要返回局部对象的引用或指针

因为函数结束后,局部变量就被释放掉了。

内存耗尽的new

内存耗尽时,new会抛出std::bad_alloc错误
但是我们可以改变new的使用方式来阻止

int *p1 = new (nothrow) int; //如果失败,返回一个空指针

赋值运算符

通常实现是:先深拷贝右侧的值到临时变量,然后销毁左侧的值,再调用左侧的拷贝构造。目的是为了防止两侧指向同一个地址,如果先销毁左侧的值,可能右侧会访问到野指针。

右值引用

为了防止在使用临时变量的过程中产生不必要的数据拷贝,引入了“对象移动”的概念。

简单理解就是移交一些临时变量的所有权,在程序中一些变量的生存期非常短,比如 部分表达式产生的值,后增运算符i++,函数返回值等等,而我们可以在他们即将销毁之前把他们的所有权“移动”(转移)给其他变量,它们在内存中的位置是不变的,也就是没有创建新的对对象,只不过归属于其他变量了,生存期也就随着所属的变量而变化了。

#include <iostream>
#include <string>
 
std::string foo()
{
    return std::string("abc");
}
 
int main()
{
    std::string s3 = foo();  //这样的写法会调用拷贝构造函数
    const std::string &s = foo();  //可以使用这个值,但是不能修改他
    std::string &&s2 = foo();   //右值引用
}

完美转发

  • std::forward()
  • std::forward<>()

当用于一个指向模板参数类型的右值引用函数参数(T&&)时,forward会保持实参类型的所有细节

// 完美转发的翻转函数
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
    f(forward<T2>(t2), forward<T1>(t1));
}

其他tips

  1. 大的类型变量避免拷贝,因为拷贝效率底,传参的时候,尽可能用引用
  2. IO对象不能拷贝和赋值(包括作为返回参数)
  3. forward_list增加和删除接口和其他容器不同,根本原因是无法访问前驱
  4. list和forward_list优先使用成员函数,而不是通用算法
  5. 如果我们用不到某个形参,那我们不需要为其命名
  6. 基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
  7. 如果一个函数参数是指向模板类型参数的右值引用(如T&&),它对应的实参的const属性和左右值属性将得到保持
  8. 调用析构函数会销毁对象,但是不会释放内存
posted @ 2022-01-17 00:46  二律背反GG  阅读(78)  评论(0编辑  收藏  举报