c++杂乱笔记1

const(不可变)

作用

1. 修饰常量
2. 修饰指针  指针常量和常量指针
3. 常量引用  避免拷贝,避免对值的修改,不能使用非const引用指向const对象。
4. 修饰成员函数   函数内部不能修改成员函数

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const int a1;
int get() const;
const char* p1 = a2;//常量指针,指向常量的指针,保护指向的地址空间数据不可变。指针可变,可以修改const指针所指向的地址,但是不能通过const对象指针来进行,自以为指向const的指针。
char* p = a2;
*p = a3;
char* const p2 = a3;//指针常量, 保护指针,指针自身的地址空间不可变,数据可变。
//不能把常量的地址赋给指针变量
void get(const int val);
void get(const int *val);
void get(int *const val);

//file1.cpp
int cout;
const int cout2;
extern const int cout3;
//file2.cpp
extern int cout; //在全局作用域里定义非const变量时,它在整个程序中都可以访问
cout++;
cout2; //error除非特别说明,在全局作用域声明的const变量是定义该对象的文件的局部变量,只存在于那个文件,不能被其他文件访问。
extern const int cout3;
cout3++;

static()

作用

1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前分配空间,如果有初始值就用初始值初始化,如果没有初始值系统用默认值初始化。
2. 修饰普通函数,该函数仅在定义该函数的文件内才能使用。
3. 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
4. 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,使用类名作用域符号访问,但是在 static 函数内不能访问非静态成员。

this指针

1. 指向非静态成员的特殊指针。
2. 当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用 this 指针。
3. this 指针被隐含地声明为: ClassName *const this,这意味着不能给 this 指针赋值。在 ClassName 类的 const 成员函数中,this 指针的类型为:const ClassName* const,这说明不能对 this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作)。
4. this 只是一个右值,不能使用&this.
5. 在以下场景中,经常需要显式引用 this 指针:
    1. 为实现对象的链式引用。
    2. 为避免对同一对象进行赋值操作。
    3. 在实现一些数据结构时,如 list。

inline

特征

1. 相当于直接执行函数体。
2. 相当于宏操作,但是多了类型检查。
3. 编译器一般不内敛包含循环,递归,switch等复杂操作的inline函数。
4. 在类声明中定义的函数,除了虚函数的成员函数会自动隐式地当成inline函数。

使用

1
2
3
4
5
// 类外定义需要显示inline
class A{
void b();
}
inline void A::b(){};

编译器对inline函数的处理过程

1. 函数体复制到调用点。
2. 为局部变量分配内存空间。
3. 将inline函数中的输入参数和返回值映射到调用方法的局部变量空间。
4. 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 goto)。

优劣

优点

1. 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
2. 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
3. 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
4. 内联函数在运行时可调试,而宏定义不可以。

缺点

1. 代码膨胀,省去了调用函数的开销。关键在于函数体内执行时间和调用函数开销的平衡。
2. inline函数无法随着函数库升级而升级,inline函数改变需要重新编译,无法直接链接。
3. 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

tips:

  1. virtual可以inline,但是当virtual表示多态性时,不能内联。
  2. inline是编译器建议编译器内联,在程序运行之前,但是多态性的表现在程序运行中。
  3. 当编译器明确知道调用对象是哪一个类时,具有实际对象,而不是对象指针或引用,可以使用 inline virtual
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class A{
    public:
    virtual void foo(){};
    }
    class B:A{
    public:
    virtual void foo(){};
    }
    A *a = new B();
    a->B::foo();
    delete a;
    a = nullptr;

volatile

1
volatile int a = 1;
1. volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。
2. volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)
3. const 可以是 volatile (如只读的状态寄存器)
4. 指针可以是 volatile
1
2
3
4
5
6
7
8
## assert()
断言,是宏,而非函数。assert 宏的原型定义在 <assert.h>(C)、<cassert>(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG 来关闭 assert,但是需要在源代码的开头,include <assert.h> 之前。

### 使用
```c++
# define NDEBUG
# cinlude <cassert>
assert(p!=NULL);

pragma pack(n)

强制设定struct,union,class成员变量以n 字节对齐方式。

1
2
#pragma pack(push)
#pragma pack(8)

位域

1
Bit mode:2;

类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。

  • 位域内存布局和机器有关
  • 类型必须是整型或者枚举,signed int根据具体实现而定。
  • 取地址和指针不能作用于位域。

extern “C”

按照C语言方式编译和链接。
extern “C” 的作用是让 C++ 编译器将 extern “C” 声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。

1
2
3
4
5
6
7
#ifdef _cp
extern "C"{
#endif
void *memeset(void *,int ,size_t);
#ifdef _cp
}
#endif

struct

c

1
2
3
4
5
typedef struct a{}s;
// 等价于
struct a{};
typedef struct a s;
// 但两个标识符名称空间不相同。

cpp

编译器定位符号的规则改变,首先搜索全局标识符表,如果没有将会搜索类标识符表。

没有同名类 A,可省略struct

1
2
struct A{};
void m(A m);

如果有同名类 A,则A 代表类A,struct A 代表结构体A。

  • class和struct 本质区别是默认的访问权限,struct默认public,class默认private。

union

联合(union)是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:
* 默认权限public
* 可以含有析构函数,构造函数
* 不能含有引用类型的成员
* 不能作为derived class,不能作为base class
* 不能含有virtual
* 匿名union在定义所在的scope可直接访问union member
* 匿名union不能包含protected和private member
* 全局匿名联合必须是static。

explicit

  • 修饰构造函数,防止隐式转换和复制初始化。
  • 修饰转换函数,防止隐式转换,按语境转换除外。
  • 按语境转换
    1. if、while、for 的控制表达式;
    2. 内建逻辑运算符 !、&& 和 || 的操作数;
    3. 条件运算符 ?: 的首个操作数;
    4. static_assert 声明中的谓词;
    5. noexcept 说明符中的表达式;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      class A{
      A(int a){}
      operator bool() const {return false;}
      };
      class B{
      B(int b){}
      explicit operator bool() const{return false;}
      };
      void getA(A a){}
      void getB(B b){}
      int main(){
      A a1(1);//直接初始化
      A a2 = 1;//复制初始化
      A a3{1};//直接列表初始化
      A a4 = {1};//复制列表初始化
      A a5 = (A)1;//允许static_cast 显示转型
      getA(1);//允许int到A的隐式转换
      if (a1);//使用转换函数A::operator bool() 的从A到bool的隐式转换
      bool a6(a1);//使用转换函数 A::operator bool()的从A到bool的隐式转换
      bool a7 = a1;//使用转换函数A::operator bool()的从A到bool的隐式转换
      bool a8 = static_cast<bool>(a1);//static_cast 进行直接初始化
      B b1(1);//ok
      B b2 = 1;//false, explitct修饰的构造函数不可复制初始化,不可复制列表初始化
      getB(b1);// 被explicit修饰的构造函数的对象不允许你int到B隐式转换
      if(b1);
      bool b6(b1);
      //被explicit 修饰的转换函数的对象可以按语境转换。
      bool b7 = b1; false,被explicit修饰的构造函数的对象不允许B到bool的转型。
      bool b8 = static_cast<bool>(b1);
      return 0;
      }

引用

1) 左值引用声明符:声明 S& D; 将 D 声明为到 声明说明符序列 所确定的类型 S 的左值引用。
2) 右值引用声明符:声明 S&& D; 将 D 声明为到 声明说明符序列 所确定的类型 S 的右值引用。
* 不存在 void 的引用,也不存在引用的引用,引用的数组,指向引用的指针.
* 引用坍缩:容许通过模板或 typedef 中的类型操作构成引用的引用,这种情况下适用引用坍缩(reference coolapsing)规则:右值引用的右值引用 坍缩成右值引用,所有其他组合均 坍缩成左值引用;
* 当函数的返回值是左值引用时,函数调用表达式成为左值表达式:
1
2
3
4
5
6
7
typedef int&  lref;
typedef int&& rref;
int n;
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&

左值引用

常规引用,一般表示对象的身份。

右值引用

右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。
右值引用可实现转移语义(Move Sementics)和精确传递(Perfect Forwarding),它的主要目的有两个方面:
消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
能够更简洁明确地定义泛型函数。
右值引用可用于为临时对象延长生存期(左值引用亦能延长临时对象生存期,但不能通过左值引用修改)

成员初始化列表

有些场合必须要用初始化列表:
1. 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2. 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3. 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化
Donate comment here