类
现实世界中的实体可以抽象出类别的概念。对应于计算机世界就有一个类(class)的概念,因为类是一个抽象的概念的对应体,所以计算机不给它分配内存,只有当类实例化为对象时,给对象分配内存
类是设计的产物,对象是对类的实例化,设计类型时并没有给其开辟空间,实例化为对象后开空间,我们操作的是对象,而不是类
C++类的设计
C++中,类是一种数据类型,客观事物是复杂的,要描述它必须从多方面进行,也就是用不同的数据类型来描述不同的方面。类把数据(事物的属性)和函数(事物的行为、操作)封装为一个整体,如商场中的商品可以这样描述:
- 商品名称(用字符串描述),该商品数量(用整数描述), 该商品单价(用浮点数描述),该商品总价(用浮点数描述)。
class CGoods { public: char Name[21]; int Amount; float Price; float Total_value; };//最后的分号不可少
- 关键字class是数据类型说明符,指出下面说明的是类。标识符CGoods是商品这个类的类型名。花括号中是构成类体的一系列成员,关键字public是一种访问限定符
访问限定符有三种:private,protected、public,如果在类起始点无访问说明符号,系统默认为private
设计一个类的一般格式为:
class 类名
{
private:
成员表1;
public:
成员表2;
protected:
成员表3;
};
成员函数的定义
函数定义通常在类的说明之后进行,其格式如下:
返回值类型 类名::函数名(参数表)
{
//函数体
}
:: 称为作用域解析运算符,它指出该函数是属于那一个类的成员函数
对象(instance)
对象是类的实例。声明一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。设计出来的类只是一个样板,以此样板可以在内存中开辟出同样结构的实例——对象。
创建类的对象可以有两种方法:
- 直接定义类的实例对象,创建在栈区:CGoods Car; int I;
- 动态创建类的对象,创建在堆区:CGoods *p = new CGoods();
有两种方法存储对象:
第一种方法是为每一个对象都分配全套的内存来存属性和方法,包括安放成员数据的数据区和安放成员函数的代码区
但是区别同一个类的各个不同对象的属性是由数据成员决定的,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有的对象都是一样的,因而为每个对象都存储一份函数代码会造成空间上的浪费
第二种方法仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象所共用
C++采用第二种方法存储对象。不同的对象有各自的属性,但是方法都是同一个方法,那么方法是怎么区别它操纵的到底是哪个对象的数据呢?这就是this指针的来源与用处所在
1.C++面向对象在内存中是如何分布的
- 对于一个对象而言,成员变量和成员函数是分开存放的
- 成员函数位于代码段,所有的类对象共有
- 成员变量为每一个对象独有,位于内存中
- 类对象在内存中的分布和struct完全相同
- 对于继承,子类的对象是在父类的对象的基础上,加上子类自己的成员
2.设计类型时,这个类型存放在哪个位置
- 类型定义(没有成员函数),编译的时候,放在编译器的符号表里,如果这个类型既没有静态成员变量,也没有定义变量常量(包括引用),也没有被函数使用(不是函数参数表中某个参数类型的一部分),没有用来定义其他类型的成员变量(常量)且不是一个嵌套类的成员,那么编译后的代码中,一般不会包含这个类型的信息.
也就是说符号表这部分,可能会被丢弃
结构定义也是一种类型定义. 类型定义本身.相当于声明
this指针
C++编译器对C++编译的时候,有三个步骤:
- 识别类里面的属性成员
- 识别方法,只识别方法的声明
- 改写类的方法(函数)成员
改写的具体有如下:
对于如下方法:
void CGoods::RegisterGoods(char name[], int amount, float price)
{
strcpy(Name, name);
Amount = amount;
Price = price;
}
编译器会改写为:
void CGoods::RegisterGoods(CGoods *const this, char name[],int amount,float price)
{
strcpy(this->Name, name);
this->Amount = amount;
this->Price = price;
}
在类里面识别到的属性和类成员函数属性同名时,就会加上this指针,这是编译器编译时自动加的,我们也可以手动加,编译时系统就不给你加了
在主函数中调用类的成员函数时:
int main()
{
CGoods x;
CGoods y;
x.RegisterGoods("C++",16,94);
y.RegisterGoods("Java",10,88);
}
调用的那两句就等价于
RegisterGoods(&x,"C++",16,94);
RegisterGoods(&y,"Java",10,88);
这样,通过this指针就解决了上述的问题:“ 不同的对象有各自的属性,但是方法都是同一个方法,那么方法是怎么区别它操纵的到底是哪个对象的数据呢 ”
下面给出this指针的基本定义:
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。
每一个对象都隐式包含一个指针,指向对象自身。当对象调用成员函数时,会默认将对象自身传递给该函数,在函数体内不直接使用该对象名,而是使用this指针,即this指针指向该对象自身,即指向调用者
什么时候需要明确的写出this指针
防止命名冲突时可以显示地指出this指针,还有想将对象本身的指针或者引用给别的函数时就需要明确的写出this指针
内联函数
直接在类内定义的函数或用inline关键字修饰的定义在类外的函数称为内联函数,内联函数在调用时直接拷贝一份过去,提高了效率,以空间换时间
如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方,因而对内联函数进行任何修改,都需要重新编译函数的所有客户端
inline关键字只是给编译器内联的建议,至于会不会真的内联,编译器会根据函数具体规模权衡时间空间效率之后选择是否内联
定义为内联的意义
当函数体积小、常被调用、所从事的计算并不复杂时可以被定义为内联函数
C++处理内联函数方法是编译时将函数调用替换成内联函数的代码,程序执行时没有发生实际的函数调用,这样相当于用空间去换时间,提高了性能,因此一般将函数体较短,需频繁调用的函数定义成内联函数,这样代码空间不会膨胀的很厉害,同时程序的执行效率也会提高
内联函数与宏方式定义的有什么区别
1)宏是预编译阶段处理的,纯粹是字符串替换,没有任何的类型检查等,十分的不安全;而inline内联函数的处理是发生在编译阶段的,有完整的语句类型检查,比宏更安全
2)宏是无法调试的,inline函数在debug版本下和普通函数一样,出了问题很方便进行断点调试,定位问题
3)大量的宏很不方便去阅读源码,inline函数和普通函数一样,结构模块化清晰,方便阅读代码
4)用宏来代替函数定义,替换后还是一个正常的函数调用,有函数调用开销(栈帧开辟和回退);而用inline内联函数是在编译时期,在函数调用点,把函数的代码直接展开,省去了函数调用的开销,代码运行效率高