C++对程序的编译过程
①识别类中的属性成员(记录在案)
②识别类中的方法(只识别方法的声明(原型))形参名可以不给,形参类型必须给;
③改写:(添加this指针)包括该函数形参列表、以及各个函数体内属于类中属性的成员前都加上this
指针。
//改写前:
void CGoods::RegisterGoods(char name[], int amount, float price)
//改写后:
void CGoods::RegisterGoods( CGoods * const this,char name[], int amount, float price)
this指针必须加上const,防止进入函数后被人为修改
当定义如下对象,调动成员函数时,实质是对象将自己的地址传递给this指针,再完成对该对象的相关操作
CGoods x;
x.RegisterGoods("C++",2,16);
//实质是:
RegisterGoods(&x,"C++",2,16);
调动成员函数时this指针存在,当调用完成this指针就不存在了
综上所述:this指针不存在与对象中,而存在与函数编译(或者说是调动成员函数时)产生的。
下面来了解一下:对于成员函数前添加const和成员函数后添加const的区别:
比如:
public:
const float fun();
这样书写表示该成员的返回值是一个常性的;
public:
float fun()const;
这样书写表示约束该方法是一个常方法
,指的是在该方法中只能够对对象的属性进行读操作,不能修改,其本质仍然是发生了如下的改写,指向为常性:
const float fun(const 类名 * const this)
同时还要注意:实例化出的常对象
只可以调动常方法,因为如果常对象调用了普通方法,改写后的this指针虽自身无法改变,但可以通过普通方法改变该常对象的属性成员,违背了常对象的性质,因此常对象只能常方法,而普通对象即可以调动常方法,也可以调动普通方法,在两种方法同时存在时,优先调用普通方法。
同时,在一个类所属的常方法和普通方法中,常方法一般来说不能够调动普通方法,因为在该过程中,能力得到了扩展,很有可能普通方法会对该对象的属性值进行修改,这违背了常方法的特性,然而普通方法却可以调动常方法,这一过程中能力收缩。
启发:因此在今后的代码编写过程中,我们应该记得在不对对象成员进行修改的函数中,尽量加上const修饰,使其通用性加大。
//实现双向函数
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x) {}
~Object() {}
void SetValue(int x) { value = x; }
int GetValue() { return value; }
// 使用一个函数实现 SetValue 和 GetValue() 函数的功能
int& Value()
{
return value;
}
const int& Value() const
{
return value;
}
};
int main()
{
Object obj(10);
int x = 0;
x = obj.Value();//x = obj.value;
obj.Value() = 100;//obj.value = 100
}
this指针的传递——thiscall调用约定
thiscall仅仅应用于C++成员函数,this指针存放于ECX寄存器
,参数从右往左
压,它不是关键字,因此不能被程序员指定。
调用成员函数时:
该成员的地址首先存入ECX寄存器中,到达函数内部后,再将ECX的值传递给this指针。
汇编层面的逻辑代码:
mov ecx,[形参]
push ecx
lea ecx,[对象名]//获取该对象的地址
call 类名::成员函数 ()
//上一句相当于mov [this],ecx;//进入成员函数内部,将ecx的值赋值给this指针
若采用C的调用约定__cdecl
(在函数声明的函数名前添加)此时采取入栈的方式:注意关注这两种调用约定的区别:
mov ecx,[形参]
push ecx
lea ecx,[对象名]//获取该对象的地址
push ecx
call 类名::成员函数 ()
内联inline
凡是在类中定义函数的声明和函数的定义时,编译器默认将此函数定为内联函数
调动一次函数,就拷贝一份函数体过来(将函数体拷贝到调动点上,不用现场保护,现场恢复,不用开辟函数栈帧和清除栈帧),没有开销,使得处理函数的速度加快,缺点是每调动一次,就需要将函数展开一次,典型的空间换时间。
在编译过程中,就没有函数的调用开销了,在函数的调用点直接把函数的代码展开处理了。
inline只是一种建议的编译方案,至于是否使用:
- 如果函数体过大,不采取该方式;
- 若函数中含有if语句,循环语句,也不采取该方案;
因此并不意味这添加了inline就采取内联方式,这与编译器有关
inline必须在Release模式下会生效,在Debug模式下不会产生内联。
内联函数和宏的区别
- inline函数的处理时机是在编译阶段处理的,有安全检查,而宏的处理是在预编译阶段处理的,没有任何的检查机制,只是简单的文本替换;
- inline是一种更安全的宏
内联函数和static修饰的函数的区别
static修饰的函数只是将函数变成局部,函数的处理机制和普通函数相同,都有函数的开栈和清栈的开销,内联函数没有函数的开栈和清栈的开销。
using指示符
using指示符是将using指示符后面的名字空间作用域下的所有符号暴露在指示符所在的作用域,因为using指示符号会导致名字空间作用域被污染,所以我们在编码过程中一般并不提倡使用using指示符
- 如果在同一个项目下出现相同的名字空间作用域,这种情况下同名的空间作用域会进行合并,包含所用空间作用中的内容。