(42)类的基本思想是数据抽象和封装,数据抽象是一种依赖于接口和实现分离的编程技术。
类的接口包括用户所能执行的操作;类的实现则包括累的数据成员、负责接口实现的函数体及定义类所需的各种私有函数
(43)this指针
在建立对象时,系统会为每个对象分配独立的存储空间,给每个对象中的数据成员都分配自己独立的存储空间。
如果对同一个类定义n个对象,则有n组同样大小的空间以存放对象中的数据成员;但对于成员函数来说,函数的代码段在内存中只有一份。一个类中的不同对象在调用自己的成员函数时,其实他们调用的是同一段函数代码。
当一个对象调用自己的成员函数时,如何保证成员函数中对数据成员的处理是针对自己的数据成员而不是其他对象的数据成员呢?
假设用一个类定义10个对象:编译系统并不是分别为10个对象的数据成员和成员函数分配存储单元。而是仅为每个 对象的数据成员分配存储单元,10个对象的成员函数对应的是同一个函数代码段。不论成员函数在类内定义还是类外定义,成员函数都用以上方式存储。其实,在每个成员函数中都包含一个特殊的指针this,它的值是当前被调用的成员函数所在对象的起始地址。
在调用成员函数时,系统隐式地将对象的其实地址传给成员函数,使this指针得到当前对象地址,于是在成员函数中对数据成员的引用,就按照this的指向找到对象的数据成员,实现对数据成员的操作。
(44)静态成员static int h,它为所有对象共有。即使有多个对象,h在内存中只占一份空间。h的值对所有的对象都是一样的,若改变h,则各对象h都同时改变,静态数据成员可以初始化,但只能在类外进行初始化,形式为:数据类型类名::静态数据成员名=初值。静态数据成员既可以通过对象名引用,也可以通过类名来引用。静态数据成员是在程序编译时被分配,结束时才释放。
静态成员用途是定义类的各个对象的公共数据,静态成员函数不是为了对象之间沟通,而是为了能处理静态数据成员。静态成员函数可以访问静态成员数据,而不能访问非静态成员。
(45)友元,就是一个类主动声明本类以外的某个函数是它的朋友,进而给它们提供访问本类成员的特许
友元:类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。
友元包括友元函数和友元类两种:
如果在本类中用friend对本类以外的某个函数进行了声明,则该函数就成为本类的友元函数。这样,它虽然不是本类的成员函数,但它可以访问本类的所有成员,无论是公有成员还是私有成员。声明友元函数可以上不属于任何类的普通函数,也可以是其他类的成员函数
如果在本类中(例如A类对另一个类例如B)进行声明,则B类就是A类的友元类,这时b中所有函数就自动成为A的友元函数
(46)引入const成员函数,紧随参数列表之后的const关键字,const 的作用是修改隐式this指针类型。
默认情况下,thist的类型是指向类类型非常量版本的常量指针。例如,在Sales_data成员函数中,this的类型是Sales_data *const。尽管this是隐式的,但它仍需要遵循初始化规则,意味着我们不能把this绑定到常量对象上。这一情况也就使得我们不能在一个常量对象上调用普通成员函数。
(47) 使用class和struct唯一区别就是默认访问权限,当我们希望定义类所有成员是public,使用struct;希望是private使用class
(48)构造函数,如果提供了一个构造函数,编译器将不会自动生成默认的构造函数。如果我们的类需要构造函数,必须显示的声明出来
Screen()=default;
(49) 构造函数的初始值,有时候必不可少,如果成员是const或者引用的话,必须初始化;类似的,如果成员属于某种类类型且该类没有定义默认构造函数,也必须将这个成员初始化。
class ConstRef{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
}
ci,ri必须初始化,如果没有为它们提供构造函数初始值将引发错误
ConstRef::ConstRef(int ii)
{
i = ii;
ci = ii;//错误,不能给const赋值
ri = i;//错误,ri没有初始化
}
如果成员是const,引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数的初始列表为这些成员提供初值
ConstRef ::ConstRef(int ii):i(ii),ci(ii),ri(ii){}
初始化成员列表,不限定初始化的具体执行顺序。初始化顺序跟类中出现的顺序一致。
(50)隐式转换
在类中定义的构造函数,决定了它的隐式转换规则
如,在Sales_data中,接受string的构造函数和接受istream的构造函数,分别定义了从这两种类型向Sales_data的隐式转换规则
也就是说,在需要使用Sales_data的地方,我们可以使用string或者istream代替
只允许一步类类型转换:编译器只会自动的进行一步类型转换
item.combine("9-999-99999-9");
错误,需要用户定义两种转换(1):把“9-999-99999-9”转换成string (2):再把这个临时的string转换成Sales_data如果想完成上述调用,可以显示的把字符串转换成string或者Sales_data
item.combine(string("9-999-99999-9"));
item.combine(Sales_item("9-999-99999-9"));
explicit禁止隐式转换:只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit,在类外定义时不应重复。
explicit构造函数只能使用直接初始化,不能用于拷贝初始化
(51)类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据,类似的,静态成员函数也不与任何对象绑定在一起,它们不包含this指针,不能再static函数中使用this指针,这一限制既适用于this的显示使用,也对调用非静态成员的隐式使用有效。
Class Account{
public :
void calculate(){amount += amount*interestRate;}
static double rate(){return interestRate;}
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
}
访问静态成员
使用作用域运算符直接访问静态成员
double r;
r = Account::rate();
虽然静态成员不属于类的某个对象,但我们仍可以使用类的对象、引用或者指针来访问静态成员
Account ac1;
Account *ac2 = &a1;
r = ac1.rate();
r = ac2->rate();