本文是对该 博客的学习笔记,鸣谢博主
总结:
- this指针是类对象成员函数在被调用的时候,隐式地传递给成员函数的,用于在成员函数体中给类对象的成员变量。
- inline是建议编译器做内联,注意是建议,用于提高程序运行效率
- volatile修饰的变量不会被编译器优化,volatile和const一样是类型修饰符
- sizeof对数组,得到整个数组所占空间大小; sizeof对指针,得到指针本身所占空间大小
this指针
this指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的那个对象。
当对一个对象调用成员函数时,编译程序先将对象的地址赋值给this指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用this指针。
当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。
this指针被隐含地声明为:ClassName *const this,这意味着不能给this指针赋值;在ClassName类的const成员函数中,this指针的类型为:const ClassName *const,这说明this指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作)
this并不是一个常规变量,而是个右值,所以不能取得this的地址(不能&this)
在以下场景中,经常需要显式引用this指针:
为实现对象的链式引用;
为避免对同一对象进行赋值操作;
在实现一些数据结构时,如list
inline内联函数
特征
相当于把内联函数里面的内容写在调用内联函数处
相当于不用执行进入函数的步骤,直接执行函数体
相当于宏,却比宏多了类型检查,真正具有函数特性
编译器一般不内联包含循环、递归、switch等复杂操作的内联函数
在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数
使用
//声明1(加inline,建议使用)
inline int functionName(int first, int second, ...);
//声明2(不加inline)
int functionName(int first, int second, ...);
//定义
inline int functionName(int first, int second, ...){/****/}
//类定义,隐式内联
class A{
int doA(){return 0;}//隐式内联
}
//类外定义,需要显式内联
class A{
int doA();
}
inline int A::doA(){return 0;}//需要显式内联
编译器对inline函数的处理步骤
将inline函数体复制到inline函数调用点处
为所用inline函数中的局部变量分配内存空间
将inline函数的输入参数和返回值映射到调用方法的局部变量空间中
如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)
优缺点
优点
内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收、结果返回等,从而提高程序运行速度。
内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会
在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能
内联函数在运行时可调试,而宏定义不可以
缺点
代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收货会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
inline函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像non-inline可以直接链接。
是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在编译器
虚函数(virtual)可以是内联函数吗?
虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联
内联是在编译时建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联
inline virtual唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如:Base::who()),这只有在编译器具有实际对象而不是队形额指针或引用时才会发生
虚函数的内联使用
#include<iostream>
using namespace std;
class Base{
public:
inline virtual void who(){
cout<<"I am Base\n";
}
virtual ~Base(){}
};
clase Derived : public Base{
public:
inline void who(){//不写inline时隐式内联
cout<<"I am Derived\n";
}
};
int main(){
//此处的虚函数who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,
//但最终是否内联取决于编译器
Base b;
b.who();
//此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联
Base *ptr = new Derived();
ptr->who();
//因为Base有虚析构函数(virtual ~Base(){}),
//所以delete时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄露
delete ptr;
ptr = nullptr;
system("pause");
return 0;
}
volatile关键字
volatile int i = 10;
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其他线程等)更改。所以使用volatile告诉编译器不应对这样的对象进行优化。
volatile关键字声明的变量,每次访问时都必须从内存中取出值(没有被volatile修饰的变量,可能由于编译器的优化,从CPU寄存器中取值)
const可以是volatile(如只读的状态寄存器)
指针可以是volatile
assert()
Assert是一个预处理宏,作用于一条表示条件的表达式。当未定义预处理变量NDEBUG时,assert对条件表达式求值,如果条件为假,输出一条错误信息并终止当前程序的执行
预处理宏,类似于内联函数的一种预处理功能,除了assert之外,现代C++程序很少再使用预处理宏了。
断言,是宏,而非函数。assert宏的原型定义在<assert.h>(C)
、<cassert>(C++)
中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义NDEBUG来关闭assert,但是需要在源代码的开头,include<assert.h>之前
assert()使用
#define NDEBUG //加上这行,则assert不可用
#include<assert.h>
assert(p!=NULL); //assert不可用
sizeof()
sizeof对数组,得到整个数组所占空间大小
sizeof对指针,得到指针本身所占空间大小
#pragma pack(n)
设定结构体、联合以及类成员变量以n字节方式对其
使用
#progma pack(push) //保存对齐状态
#progma pack(4) //设定为4字节对齐
struct test{
char m1;
double m4;
int m3;
};
#pragma pack(pop) //恢复对齐状态