C++教程之类的相关知识(七)

  作者:bea

那么虚继承呢?被虚继承的类的成员将被间接操作,这就是它的“一种手段”,也就是说操作这个被虚继承的类的成员,可能由于得到的偏移值不同而操作不同的内存。但对虚类表的修改又只限于如果重复出现,则修改成间接操作同一实例,因此从根本上虚继承就是为了解决上篇所说的鲸鱼有两个饥饿度的问题,本身的意义就只是一种算法的实现。这导致在设计海洋生物和脯乳动物时,无法确定是否要虚继承父类动物,而要看派生的类中是否会出现类似鲸鱼那样的情况,如果有,则倒过来再将海洋生物和脯乳动物设计成虚继承自动物,这不
那么虚继承呢?被虚继承的类的成员将被间接操作,这就是它的“一种手段”,也就是说操作这个被虚继承的类的成员,可能由于得到的偏移值不同而操作不同的内存。但对虚类表的修改又只限于如果重复出现,则修改成间接操作同一实例,因此从根本上虚继承就是为了解决上篇所说的鲸鱼有两个饥饿度的问题,本身的意义就只是一种算法的实现。这导致在设计海洋生物和脯乳动物时,无法确定是否要虚继承父类动物,而要看派生的类中是否会出现类似鲸鱼那样的情况,如果有,则倒过来再将海洋生物和脯乳动物设计成虚继承自动物,这不是好现象。
static(静态)
在《C++从零开始(五)》中说过,静态就是每次运行都没有变化,而动态就是每次运行都有可能变化。C++给出了static关字,和上面的public、virtual一样,只是个语法标识而已,不是类型修饰符。它可作用于成员前面以表示这个成员对于每个实例来说都是不变的,如下:
struct A { static long a; long b; static void ABC(); }; long A::a;
void A::ABC() { a = 10; b = 0; }; void main() { A a; a.a = 10; a.b = 32; }
上面的A::a就是结构A的静态成员变量,A::ABC就是A的静态成员函数。有什么变化?上面的映射元素A::a的类型将不再是long A::而是long。同样A::ABC的类型也变成void()而不是void( A:: )()。
首先,成员要对它的类的实例来说都是静态的,即成员变量对于每个实例所标识的内存的地址都相同,成员函数对于每个this参数进行修改的内存的地址都是不变的。上面把A::和A::ABC变成普通类型,而非偏移类型,就消除了它们对A的实例的依赖,进而实现上面说的静态。
由于上面对实例依赖的消除,即成员函数去掉this参数,成员变量映射的是一确切的内为A::a,类型为long,映射的地址并没有给出,即还未定义,所以必须在全局空间中(即不在任何一个函数体内)再定义一遍,进而有long A::a;。同样A::ABC的类型为void(),被去除了this参数,进而在A::ABC中的b = 10;等同于A::b = 10;,发现A::b是偏移类型,需要this参数,则等同于this->A::b = 10;。结果A::ABC没有this参数,错误。而对于a = 10;,等同于A::a = 10;,而已经有这个变量,故没任何问题。
注意上面的a.a = 10;等同于a.A::a = 10;,而A::a不是偏移类型,那这里不是应该报错吗?对此C++特别允许这种类型不匹配的现象,其中的“a.”等于没有,因为这正是前面我们要表现的静态成员。即A a, b; a.a = 10; b.a = 20;执行后,a.a为20,因为不管哪个实例,对成员A::a的操作都修改的同一个地址所标识的内存。
什么意义?它们和普通的变量的区别就是名字被A::限定,进而能表现出它们的是专用于类A的。比如房子,房子的门的高度和宽度都定好了,有两个房子都是某个公司造的,它们的门的高度和宽度相同,因此门的高度和宽度就应该作为那个公司造的房子的静态成员以记录实际的高度和宽度,但它们并不需要因实例的不同而变化。
除了成员,C++还提供了静态局部变量。局部变量就是在函数体内的变量,被一对“{}”括起来,被限制了作用域的变量。对于函数,每次调用函数,由于函数体内的局部变量都是分配在栈上,按照之前说的,这些变量其实是一些相对值,则每次调用函数,
可能由于栈的原因而导致实际对应的地址不同。如下:
void ABC() { long a = 0; a++; } void BCD() { long d = 0; ABC(); }
void main() { ABC(); BCD(); }
上面main中调用ABC而产生的局部变量a所对应的地址和由于调用BCD,而在BCD中调用ABC而产生的a所对应的地址就不一样,原理在《C++从零开始(十五)》中说明。因此静态局部变量就表示那个变量的地址不管是通过什么途径调用它所在的函数,都不变化。如下:
void ABC() { static long a = 0; a++; } void BCD() { long d = 0; d++; ABC(); }
void main() { ABC(); BCD(); }
上面的变量a的地址是固定值,而不再是原来那种相对值了。这样从main中调用ABC和从BCD中调用ABC得到的变量a的地址是相同的。上面等同于下面:
long g_ABC_a = 0; void ABC() { g_ABC_a++; } void BCD() { long d = 0; d++;
ABC(); }
void main() { ABC(); BCD(); }
因此上面ABC中的静态局部变量a的初始化实际在执行main之前就已经做了,而不是想象的在第一次调用ABC时才初始化,进而上面代码执行完后,ABC中的a的值为2,因为ABC的两次调用。
它的意义?表示这个变量只在这个函数中才被使用,而它的生命期又需要超过函数的执行期。它并不能提供什么语义(因为能提供的“在这个函数才被使用”使用局部变量就可以做到),只是当某些算法需要使用全局变量,而此时这个算法又被映射成了一个函数,则使用静态变量具有很好的命名效果--既需要全局变量的生存期又应该有局部变量的语义。
inline(嵌入)
函数调用的效率较低,调用前需要将参数按照调用规则存放起来,然后传递存放参数的内存,还要记录调用时的地址以保证函数执行完后能回到调用处(关于细节在《C++从零开始(十五)》中讨论),但它能降低代码的长度,尤其是函数体比较大而代码中调用
它的地方又比较多,可以大幅度减小代码的长度(就好像循环10次,如果不写循环语句,则需要将循环体内的代码复制10遍)。但也可能倒过来,调用次数少而函数体较小,这时之所以还映射成函数是为了语义更明确。此时可能更注重的是执行效率而不是代码长度,为此C++提供了inline关键字。
在函数定义时,在定义语句的前面书写inline即可,表示当调用这个函数时,在调用处不像原来那样书写存放、传递参数的代码,而将此函数的函数体在调用处展开,就好像前面说的将循环体里的代码复制10遍一样。这样将不用做传递参数等工作,代码的执行效率将提高,但最终生成的代码的长度可能由于过多的展开而变长。如下:
void ABCD(); void main() { ABCD(); } inline void ABCD() { long a = 0; a++; }
上面的ABCD就是inline函数。注意ABCD的声明并没有书写inline,因为inline并不是类型修饰符,它只是告诉编译器在生成这个函数时,要多记录一些信息,然后由连接器根据这些信息在连接前视情况展开它。注意是“视情况”,即编译器可能足够智能以至于在连接时发现对相应函数的调用太多而不适合展开进而不展开。对此,不同的编译器给出了不同的处理方式,对于VC,其就提供了一个关键字__forceinline以表示相应函数必须展开,不用去管它被调用的情况。
前面说过,对于在类型定义符中书写的函数定义,编译器将把它们看成inline函数。变成了inline函数后,就不用再由于多个中间文件都给出了函数的定义而不知应该选用哪个定义所产生的地址,因为所有调用这些函数的地方都不再需要函数的地址,函数将直接在那里展开。
const(常量)
前面提到某公司造的房子的门的高度和宽度应该为静态成员变量,但很明显,在房子的实例存在的整个期间,门的高度和宽度都不会变化。C++对此专门提出了一种类型修饰符--const。它所修饰的类型表示那个类型所修饰的地址类型的数字不能被用于写操作,
即地址类型的数字如果是const类型将只能被读,不能被修改。如:const long a = 10, b = 20; a++; a = 4;(注意不能cosnt long a;,因为后续代码都不能修改a,而a的值又不能被改变,则a就没有意义了)。这里a++;和a = 4;都将报错,因为a的类型为cosnt long,表示a的地址所对应的内存的值不能被改变,而a++;和a = 4;都欲改变这个值。
由于const long是一个类型,因此也就很正常地有const long*,表示类型为const long的指针,因此按照类型匹配,有:const long *p = &b; p = &a; *p = 10;。这里p = &a;按照类型匹配很正常,而p是常量的long类型的指针,没有任何问题。但是*p = 10;将报错,因为*p将p的数字直接转换成地址类型,也就成了常量的long类型的地址类型,因此对它进行写入操作错误。
注意有:const long* const p = &a; p = &a; *p = 10;,按照从左到右修饰的顺序,上面的p的类型为const long* const,是常量的long类型的指针的常量,表示p的地址所对应的内存的值不能被修改,因此后边的p = &a;将错误,违反const的意义。同样*p = 10;也错误。不过可以:
long a = 3, *const p = &a; p = &a; *p = 10;
上面的p的类型为long* const,为long类型的常量,因此其必须被初始化。后续的p = &a;将报错,因为p是long* const,但*p = 10;却没有任何问题,因为将long*转成long后没有任何问题。所以也有:
const long a = 0; const long* const p = &a; const long* const *pp = &p; 有用  |  无用

猜你喜欢