c++笔记(一)

一、默认情况下,const对象仅在文件内有效,多个文件出现同名const变量,视为不同文件分别定义的独立变量。若要在不同文件间共享const对象,需加extern关键字。

二、指向常量的指针或引用(所谓low-level const)可以绑定到一个非常量上。

三、copy时,两个对象必须具有相同的low-level const资格,或能进行相关转换(窄向转换,对对象的转换朝限制更严格的方向)。

四、当使用类型别名时,注意数据的基本类型:

 typedef char* pstring; 
 const pstring cstr;//此处cstr是指向char的常量指针,const是顶层const
                    //并不能直接按展开来看,此处基本数据类型是pstring
                    //即char* 

五、auto类型符会忽略top-level const,会保留low-level const 。

六、引用总是作为其所指对象的同义词出现,但是在decltype处是个例外:

  const int ci = 0,cj = 1,
  &cr = ci;   decltype(cr) x = cj;//此处x类型为引用,必须初始化 

七、decltype((variable))(双层括号)结果永远是引用。
decltype作用于一个表达式,若表达式求值结果是左值,则得到引用类型:

int a = 1;
int* p = &a;
decltype(*p) q = a; //*p求值结果是左值,decltype结果是int&

八、vector初始化:当使用花括号初始化但提供的初始值又不能用来列表初始化,编译器会推断用这样的值来构造合适的对象。

vector<string> v1{10};//10个空字符串
vector<string> v2{10,"hi"};//10个hi
                           //正常构造使用圆括号

九、#include<iterator> 用于数组的两个标准库函数begin()和end(),以数组名作为 参数,返回指向数组首元素和尾后元素的指针:

int* beg = begin(intarr);
int* ed = end(intarr);

十、内置下标的索引不是无符号类型,标准库类型必须是无符号类型:

int arr[] = {1,2,3,4,5}
int *p = arr[2];
int i = p[-2]; //i = arr[0]

十一、使用范围for循环处理多维数组,除了最内层循环,其他所有外层循环的控制变量都应该是引用类型,这样做是为了避免数组被自动转换为指针

int ia[row][col];
for(auto row : ia)    //此处row被转换成int*
  for(auto col : row) //error,内层循环不合法
    {                 //正确写法是auto& row
      //...
    }

十二、range-for中预存了尾后迭代器,如果需要添加/删除变量,可能会使迭代器失效,所以不能这样做。

十三、重载函数实参类型转换优先级:

  • 1.精确匹配、数组/函数->指针、增删顶层const
  • 2.const转换
  • 3.类型提升
  • 4.算术类型转换
  • 5.类类型转换

十四、定义了指向重载函数的指针,指针类型必须与重载函数中的某一个精确匹配,不能有实参或返回类型的转换。

十五、在类中定义类型的成员必须先定义后使用,这一点与普通成员有别。因为如果成员使用了外层作用域定义的一个类型名,则类在之后不能重新定义该名字,所以类型名的定义一般放在累的开始处。

//typedef int pos;
class A{
  public:
  //..
    typedef int pos; //之后不能再重新定义pos。
    //假如类外已经定义pos类型,
    //则在类内重新定义pos是错误的,尽管编译器经常忽略此错误
  //..
};

十六、当一个数据成员被定义成mutable,则在const成员函数内也能改变它的值:

class A 
{
  private:
    mutable int a;
  public:
    void f() const   //本来const成员函数内的数据成员是不允许被改变的 
    {                //但是a被mutable修饰后是可以改变的
      a++;
    }
  //..  
};

十七、避免头文件的循环引用:

  • 头文件做好 include guard,例如 pragma once。
  • 尽量使用 forward declaration,namespace 中的 class 都可以前向声明,模板也可以前向声明,例如class Foo; typedef std::shared_ptr FooPtr。但是 nested class无法前向声明。
  • 保证每个构成interface的头文件都独立可用,例如 class A 的 cpp 文件第一个包含的头文件应该是 class A的头文件,以此类推。尽量 cpp/头文件 配对:尽量每一个 class 有一个头文件和一个 cpp文件,文件名与类名相同,仅扩展名不同。(类模板除外,没办法。)
  • include 头文件使用绝对路径,从VCS的根开始。
  • cpp 文件和头文件中写 include时按一定的原则分组(例如本项目、本公司、第三方C++库、C++标准库、第三方C库,libc库),每组以内按字母顺序排列头文件。做到第3点之后,用简单的脚本就能生成头文件的包含关系图(Doxygen也行),然后就很容易看出循环依赖。也不难自动检测。
  • 头文件里不要埋地雷,比如修改 struct 的默认对齐方式,修改编译器的优化级别或警告级别等等。

十八、类负责控制自己的友元类和友元函数,友元不存在传递性。

十九、友元影响的是访问权限,并非真正意义上的声明,即使在类内部定义的友元函数,也必须要在外部提供相应的声明以确保其可见:

class Sample
{
    friend void fun() {} //内部定义的友元函数
public:
    void f();
};
void fun(); //外部声明是必要的
void Sample::f()
{
    fun();
}

二十、类的两阶段编译:

  • 编译所有成员的声明。
  • 直到类全部可见再编译函数体。

声明中使用的名字(包括返回类型和参数列表)仍然要确保使用前可见:

typedef double Money;
string bal;
class Acc{
public:
  Money balance() {return bal;} //balance编译时,Money在外层作用域可见
                                //当编译bal时,类内bal已经可见,此处bal是类内的
private:
  Money bal;
// ..
};

猜你喜欢

转载自blog.csdn.net/qq_38216239/article/details/80489747