您的位置:首页 > 编程语言 > C语言/C++

C++ 点滴积累(4)

2011-10-22 23:45 162 查看
1. 数组作形参

声明:在C/C++中,数组参数永远不会按值传递,它是传递第一个元素(准确地说是第0个)的地址,就是说形参不会复制数组。

- 例如:void putValues(int[10]);

- 被编译器视为:void putValues(int *);

- 数组的长度与参数声明无关,因此,下列三个声明是等价的:

void putValues(int *);

void putValues(int[]);

void putValues(int [10] );

因此数组被传递为指针,所以这对程序员有两层含义。
如何修改?

一个常见的机制是提供一个含有数组长度的额外参数。

例如:void putValues(int [], int size);

另外一种机制是将函数参数声明为数组的引用。

当参数是一个数组类型的引用时,数组长度成为参数类型的一部分,编译器将检查数组实参的长度是否匹配。

#include <iostream>
#include <string>
using namespace std;

void putValues(int(&arr)[10]);
void main()
{
int i, j[2],k[10] = {1};
//putValues(i);//错误:实参的类型不符
//putValues(j);//错误:实参不是10个int的数组
putValues(k);
}

void putValues(int(&ia)[10])
{
cout<<"(10)<";
for(int i = 0;i < 10; ++i)
{
cout<<ia[i];
if(i!= 9)
cout<<",";//用逗号分隔元素
}
cout<<">\n";
}

运行结果:(10)<1,0,0,0,0,0,0,0,0,0>

2.对象数组初始化

创建数组中每一个元素对象时,系统都会调用类构造函数初始化该对象。

通过初始化列表赋初值。

– 例: Point A[2]={Point(1,2),Point(3,4)};//注意理解

如果没有为数组元素指定显式初始值,数组元素便调用无参构造函数。

各元素对象的初值要求为相同值时,要声明具有默认形参值的构造函数。

各元素对象的初值要求为不同值时,要声明带形参的构造函数。

各元素对象的初值要求为不同值时,要声明带形参的构造函数。

当数组中每一个对象被删除时,系统都要调用一次析构函数。

3.为什么不能用一个内部auto变量去初始化static指针?

因为内部auto变量所对应的存储单元随函数的调用而存在,随函数的执行完毕而回收,而静态指针却长期占用内存,不随函数的调用或执行结束而释放,

当再次进入函数后该指针又成为可见的.因此,用内部auto变量的地址去初始化一个静态指针是没有意义的。

4. 类的聚集和浅拷贝与深拷贝

详见:/article/7786083.html

5.用字符数组存储和处理字符串

1)字符串的输入/输出

方法

–逐个字符输入输出

–将整个字符串一次输入或输出

例:char c[]="China";cout<<c;

注意

–输出字符不包括'\0'

–输出字符串时,输出项是字符数组名,输出时遇到'\0'结束。

输入多个字符串时,以空格分隔输入单个字符串时其中不能有空格。

例如:程序中有下列语句:

static char str1[5],str2[5],str3[5];

cin>>str1>>str2>>str3;

运行时输入数据:How are you?

内存中变量状态如下:str1: H o w \0 str2: a r e \0 str3: y o u ? \0

若改为:

static char str[13];

cin>>str;

运行时输入数据:How are you?

内存中变量str内容如下:str: H o w \0

注意!若有如下声明:

char a[4], *p1, *p2;

–错误的:

a="abc"; (数组名是常量,只能当左值用)

cin>>p1; (local variable 'p1' used without having been initialized)

–正确的:

p1="abc";

p2=a; cin>>p2;

2)整行输入字符串

cin.getline(字符数组名St, 字符个数N, 结束符);

功能:一次连续读入多个字符(可以包括空格),直到读满N个,或遇到指定的结束符(默认为'\n')。

读入的字符串存放于字符数组St中。读取但不存储结束符。

cin.get(字符数组名St, 字符个数N, 结束符);

功能:一次连续读入多个字符(可以包括空格),直到读满N个,或遇到指定的结束符(默认为'\n')。

读入的字符串存放于字符数组St中。既不读取也不存储结束符。

//整行输入字符串举例
#include <iostream>
using namespace std;
void main (void)
{
char city[80];
char state[80];
int i;
for (i = 0; i < 2; i++)
{
cin.getline(city,80,',');
cin.getline(state,80,'\n');
cout<< "City: " << city << " Country: "<< state << endl;
}
}
运行结果:

dalian, china

City: dalian Country: china

beijing, china

City: beijing Country: china

Press any key to continue

3)字符串处理函数

strcat(连接),strcpy(复制),

strcmp(比较),strlen(求长度),

strlwr(转换为小写),

strupr(转换为大写)

头文件<cstring>

strlen()函数与sizeof的差异

sizeof()是测试一个变量所占字节数;

strlen()是测试一个串有效字符个数。

char a[10] ="123456789" ,*p = a;

printf("%d\n",sizeof(a)); // 10

printf("%d\n",strlen(a)); // 9

printf("%d\n",sizeof(p)); // 4

printf("%d\n",strlen(p)); // 9

尤其是当指针作形参,字符型数组作实参时,定要搞清形参究竟得到了什么。

4)string类

面向对象的“串”,已不再是面向过程的“串”了。它是类。除了含有容纳串的字符序列外,还含有丰富的成员函数,以便提供串操作的服务。

该类在头文件string中。

#include <string>
#include <iostream>
using namespace std ;
void trueFalse(int x)
{
cout<< (x? "True": "False") << endl;
}
void main()
{
string S1="DEF", S2="123";
char CP1[ ]="ABC";
char CP2[ ]="DEF";
cout<< "S1 is " << S1 << endl;
cout<< "S2 is " << S2 << endl;
cout<<"length of S2:"<<S2.length()<<endl;
cout<< "CP1 is " << CP1 << endl;
cout<< "CP2 is " << CP2 << endl;
cout<< "S1<=CP1 returned ";
trueFalse(S1<=CP1);
cout<< "CP2<=S1 returned ";
trueFalse(CP2<=S1);
S2+=S1;
cout<<"S2=S2+S1:"<<S2<<endl;
cout<<"length of S2:"<<S2.length()<<endl;
}

6.类的继承与派生

1)继承与派生的目的

继承的目的:实现代码重用。

派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。

继承为软件的层次化开发提供了保证。

2)三种继承方式

–公有继承public (原封不动)

–保护继承protected (折中)

–私有继承private (化公为私)

继承方式影响子类的访问权限:

–派生类成员对基类成员的访问权限

–通过派生类对象对基类成员的访问权限

3)继承的工作内容

三项工作:

–吸收基类成员全盘(除了六个特别函数及静态外)接收;此项工作程序员无法干预。(不再讨论)

–改造基类成员

1.对基类成员访问权限的改变;(规则严格)

2.对基类成员的覆盖;(有技巧)

–新增派生类特有的成员“青出于蓝而胜于蓝”(自由发挥)

4)公有继承(public)

基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。

派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

通过派生类的对象只能访问基类的public成员。

(前两条属类内访问,后条属类外访问)

//公有继承举例
#include<iostream>
#include<cmath>
using namespace std;
class Point//基类Point类的声明
{
public://公有函数成员
void InitP(float xx=0, float yy=0)
{X=xx;Y=yy;}
void Move(float xOff, float yOff)
{X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private://私有数据成员
float X,Y;
};
class Rectangle: public Point //派生类声明
{
public://新增的公有函数成员
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;}//调用基类公有成员函数
float GetH() {return H;}
float GetW() {return W;}
private://新增的私有数据成员
float W,H;
};

void main()
{
Rectangle rect;
//用子类对象访问子类新增成员,间接访问了父类成员。
rect.InitR(2,3,20,10);
rect.Move(3,2);
//通过子类对象直接访问父类成员
cout<<rect.GetX()<<',' <<rect.GetY()<<','
<<rect.GetH()<<','<<rect.GetW()<<endl;
}


保护继承(protected)

基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可直接访问。

派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

通过派生类的对象不能直接访问基类中的任何成员

(前两条属类内访问,后条属类外访问)

protected 成员的特点与作用

对于本类模块来说,它与private 成员的性质相同,只供类内访问。

对于其派生类来说,它没变得不可访问,仍然保持了原来的性质。

既实现了数据隐藏,又方便了子类实现代码重用

//保护继承举例
#include<iostream>
#include<cmath>
using namespace std;
class A
{
protected:
int x;
public:
A(){x = 0;}
A(int xx):x(xx){}
void show()
{cout << x << endl;}
};

class B: protected A
{
public:
void Function();
//只写函数名,不要带函数类型。数据成员亦同。
using A::show;

};

void B::Function()
{
x = 5;
}

void main()
{
B b;
b.Function();
b.show();
}


私有继承(private)

基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问。

派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

通过派生类的对象不能直接访问基类中的任何成员。

(前两条属类内访问,后条属类外访问)

//私有继承举例
#include<iostream>
using namespace std;
class Point//基类Point类的声明
{
public://公有函数成员
void InitP(float xx=0, float yy=0)
{X=xx;Y=yy;}
void Move(float xOff, float yOff)
{X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private://私有数据成员
float X,Y;
};
class Rectangle: private Point//派生类声明
{
public://新增外部接口
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;}//访问基类公有成员
//子类要为基类提供被访问渠道。典型的“改造”
void Move(float xOff, float yOff) {Point::Move(xOff,yOff);}
float GetX() {return Point::GetX();}
float GetY() {return Point::GetY();}
float GetH() {return H;}
float GetW() {return W;}
private://新增私有数据
float W,H;
};

int main()
{
Rectangle rect;
rect.InitR(2,3,20,10);
//通过派生类对象只能访问本类成员
rect.Move(3,2);
cout<<rect.GetX()<<',' <<rect.GetY()<<','
<<rect.GetH()<<','<<rect.GetW()<<endl;
return 0;
}


由于私有继承,原有的公有成员和保护成员的访问级别升高成了私有级。若想保持原访问权限不变,可以用::将其还原,即所谓“捞”出来。但是,

第一:不能区分重载。

第二:只能还原,不能提高访问级别。

保护继承的还原亦同理。

5)继承与静态成员
类的静态成员不参与继承,当然也不受继承方式的影响。
子孙们类都可访问基类的静态成员。
类外对静态成员的访问,取决于该成员的访问权限。

6)三种继承方式的选择

若想完全保留基类的操作功能,只是扩展新功能,则用公有继承。这叫“类型继承”。

若想完全改变基类的功能,将其改头换面,做成基于原来类,但隐藏、伪装了原功能,则用私有继承。只能算“实现继承”。

若想既隐藏基类的操作功能,又能方便的传给后代,不至于难以访问,则用保护继承。也是“实现继承”。

7)类型兼容规则

一个公有派生类的对象可以替代基类的对象(反之则禁止):

–派生类的对象可以被赋值给基类对象。

–派生类的对象可以初始化基类的引用。

–指向基类的指针也可以指向派生类对象。

通过基类对象名、引用名、指针只能使用从基类继承来的成员。“窄化效应”,“切割”。

此规则又称“类型包容法则”、“向上兼容性”、“向上映射”。

“类型兼容规则”无论对于单继承还是多继承皆适用。

8)继承时的构造函数

单继承时构造函数的形式

派生类名::派生类名(基类所需的形参,本类成员所需的形参): 基类名(参数表) //初始化列表

{本类成员赋初值语句;}

多继承时的构造函数

派生类名::派生类名

( 基类1形参,基类2形参,... 基类n形参,

本类形参)

: 基类名1(参数), 基类名2(参数), ...基类名n(参数) //初始化列表

{

本类成员赋初值语句;

}

多继承且有内嵌对象时的构造函数

派生类名::派生类名

(基类1形参,基类2形参,...基类n形参,

本类形参)

: 基类1(参数), 基类2(参数), ...基类n(参数),

对象数据成员的初始化

{

本类成员赋初值语句;

}

派生类与基类构造函数的关系

当基类中未声明任何构造函数或声明了无参构造函数时,派生类构造函数可以不向基类构造函数传递参数。

若基类中未声明构造函数,派生类中也可以不声明,全采用缺省形式构造函数。

当基类声明有带参构造函数时,派生类也必须声明带参构造函数,并将参数传递给基类构造函数。

//派生类构造函数举例
#include <iostream>
using namespace std;
class B1//基类B1,构造函数有参
{
public:
B1(int i)
{cout<<"constructing B1 "<<i<<endl;}
};
class B2//基类B2,构造函数有参
{
public:
B2(int j)
{cout<<"constructing B2 "<<j<<endl;}
};
class B3//基类B3,构造函数无参
{
public:
B3()
{cout<<"constructing B3 *"<<endl;}
};
class C: public B2, public B1, public B3
{
public://派生类的公有成员
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
private://派生类的私有对象成员
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
void main()
{
C obj(1,2,3,4);
}
运行结果:

constructing B2 2

constructing B1 1

constructing B3 *

constructing B1 3

constructing B2 4

constructing B3 *

Press any key to continue

构造函数的调用次序

1.首先调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。

2.然后调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。

3.最后,执行派生类的构造函数体中的语句。

9)继承时的拷贝构造函数

若建立派生类对象时调用缺省拷贝构造函数,则编译器将自动调用基类的缺省拷贝构造函数。

若基类有显式拷贝构造函数且需要传递参数,则定要为派生类编写拷贝构造函数,以便为基类相应的拷贝构造函数传递参数。

//私有继承时的拷贝构造函数
#include <iostream>
using namespace std;
class B0//基类B0声明
{
int m;
public:
B0() { m = 10; }
B0(B0 & a) : m(a.m) { }
void display()
{cout<<"B0::display() m = "<<m<<endl;}
};
class D: private B0
{
int n;
public:
D(){ n = 20; }
//用到类型兼容规则
D(D & b):B0(b),n(b.n){}
void display()
{
B0:: display();
cout<<"D1::display()"<<" "<<n<<endl;
}
};
void main()//主函数
{
D d1;
D d2(d1);
d1.display();
d2.display();
}
运行结果:

B0::display() m = 10

D1::display() 20

B0::display() m = 10

D1::display() 20

Press any key to continue

小结:使用初始化列表的场合

当类关系是组合时,为作为成员的对象隐式调用构造函数,产生有名对象之用。此时初始化列表为构造函数传递实参。

当类关系是继承时,为作为子类组成部分的父类成员显式调用构造函数,产生无名对象之用。此时初始化列表也为构造函数传递实参。

也可以为类自身的数据成员赋初值之用。尤其是为常数据成员和引用型数据成员初始化时之用。

10)继承时的析构函数

析构函数也不被继承,派生类自行声明

声明方法与一般(无继承关系时)类的析构函数相同。

不需要显式地调用基类的析构函数,系统会自动隐式调用。

析构函数的调用次序与构造函数相反。

7. ::用法小结

详见:/article/7786085.html

8. 二义性问题

详见:/article/7786084.html

9. 多态性

1)多态性的分类



2)多态性的实现

多态的实现可分为编译时多态和运行时多态,它们分别对应静态联编和动态联编。

联编又称为绑定(binding),是指计算机程序中的语法元素(标识符、函数等)彼此相关联的过程。

从绑定的时机看,在编译时就完成的绑定叫静态绑定;直到运行时才能确定并完成的绑定叫动态绑定。

静态绑定消耗编译时间,动态绑定消耗运行时间。

静态绑定的程序到了运行阶段其功能就固定了,即使情况发生了变化,功能无法改变。

动态绑定的程序由于绑定发生在运行阶段,其功能是未定的,当情况变化了,功能也跟着变。于是表现出会聪明的判断及具有灵活的行为。

关于“抽象”

指针——地址的抽象;

形参——数值的抽象;

对象——事物的抽象;

类——对象的抽象;

超类——类的抽象;

模板——类型的抽象;

多态——行为的抽象;

函数——过程的抽象;


类型——数据标识的抽象;

异常——错误的抽象;

函数对象——函数的一元化抽象;

流——文件的抽象;

3)运算符重载

运算符重载是对已有的运算符赋予多重含义。其实是将运算符函数化。

基本认识–C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)。

实现机制–将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。

–编译系统对重载运算符的选择,遵循函数重载的选择原则。

规则和限制

可以重载C++中除下列运算符外的所有运算符: . .* :: ?: sizeof || &&

只能重载C++语言中已有的运算符,不可臆造新的。

不改变原运算符的优先级和结合性。

不能改变操作数个数。

经重载的运算符,其操作数中至少应该有一个是自定义类型,即不可重写原运算符。

不可声明为类属性。

重载函数的表现形式仅有两种:

重载为类成员函数。–要受类成员的存取权限约束

重载为友元函数。

声明形式:函数类型 operator 运算符(形参表){......}

重载为类成员函数时:形参个数=原操作数个数-1(后置++、--除外)

重载为友元函数时:形参个数=原操作数个数

运算符成员函数的设计

//将“+”、“-”运算重载为复数类的成员函数。
#include<iostream>
using namespace std;
class complex//复数类声明
{
public://外部接口
complex(double r=0.0,double i=0.0){real=r;imag=i;}
//构造函数
complex operator + (complex c); //+重载为成员函数
complex operator -(complex c); //-重载为成员函数
void display();//输出复数
private://私有数据成员
double real;//复数实部
double imag;//复数虚部
};
complex complex::operator +(complex c2) //重载函数实现
{
complex c;
c.real=c2.real+real;
c.imag=c2.imag+imag;
return complex(c.real,c.imag);
}
complex complex::operator -(complex c2) //重载函数实现
{
complex c;
c.real=real-c2.real;
c.imag=imag-c2.imag;
return c;
}
void complex::display()
{
cout<<"("<<real<<","<<imag<<")"<<endl;
}
void main() //主函数
{
complex c1(5,4),c2(2,10),c3; //声明复数类的对象
cout<<"c1=";
c1.display();
cout<<"c2=";
c2.display();
c3=c1-c2;//使用重载运算符完成复数减法
cout<<"c3=c1-c2=";
c3.display();
c3=c1+c2;//使用重载运算符完成复数加法
cout<<"c3=c1+c2=";
c3.display();
}
运行结果:

c1=(5,4)

c2=(2,10)

c3=c1-c2=(3,-6)

c3=c1+c2=(7,14)

Press any key to continue

//运算符前置++和后置++重载为时钟类的成员函数。
#include<iostream>
using namespace std;
class Clock//时钟类声明
{
public://外部接口
Clock(int NewH=0, int NewM=0, int NewS=0)
{Hour = NewH; Minute = NewM; Second = NewS;}
void SetTime(int NewH, int NewM,int NewS);
void ShowTime();
Clock & operator ++(); //前置单目运算符重载
Clock operator ++(int); //后置单目运算符重载
private://私有数据成员
int Hour,Minute,Second;
};
Clock& Clock::operator ++()//前置单目运算符重载函数
{
Second++;
if(Second>=60)
{
Second=Second-60;
Minute++;
if(Minute>=60)
{
Minute=Minute-60;
Hour++;
Hour=Hour%24;
}
}
return *this;
}
//后置单目运算符重载
Clock Clock::operator ++(int)
{
//注意形参表中的整型参数
Clock old=*this;
++(*this); //调用了另一个成员函数
return old;
}
void Clock ::SetTime(int NewH, int NewM,int NewS)
{
Hour=NewH;
Minute=NewM;
Second=NewS;
}
void Clock ::ShowTime()
{
cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
}
void main()
{
Clock myClock(23,59,59);
cout<<"First time output:";
myClock.ShowTime();
cout<<"Show myClock++:";
(myClock++).ShowTime();
cout<<"Show ++myClock:";
(++myClock).ShowTime();
}
运行结果:

First time output:23:59:59

Show myClock++:23:59:59

Show ++myClock:0:0:1

Press any key to continue

运算符友元函数的设计

运算符 定义式 等价式

双目运算符@ oprd1 @oprd2 operator @(oprd1,oprd2)

前置单目@ @ oprd operator @(oprd)

后置单目@ oprd@operator @(oprd,0 )

如果需要重载一个运算符,使之能够用于在类外操作某类对象的私有成员,可以此将运算符重载为该类的友元函数。

函数的形参代表了依自左至右次序排列的各操作数。

为了区分前置后置运算,后置单目运算符++和--的重载函数,其形参表中要增加一个int,但不必写形参名。

//将+、-(双目)重载为复数类的友元函数。
#include<iostream>
using namespace std;
class complex//复数类声明
{
public://外部接口
complex(double r=0.0,double i=0.0){real=r;imag=i;}
//构造函数
friend complex operator +(complex c1,complex c2);//运算符+重载为友元函数
friend complex operator -(complex c1,complex c2);//运算符-重载为友元函数
void display();//输出复数
private://私有数据成员
double real;//复数实部
double imag;//复数虚部
};
//运算符重载友元函数实现
complex operator +(complex c1,complex c2)
{
return complex(c2.real+c1.real, c2.imag+c1.imag);
}
//运算符重载友元函数实现
complex operator -(complex c1,complex c2)
{
return complex(c1.real-c2.real, c1.imag-c2.imag);
}
void complex::display()
{
cout<<"("<<real<<","<<imag<<")"<<endl;
}
void main() //主函数
{
complex c1(5,4),c2(2,10),c3; //声明复数类的对象
cout<<"c1=";
c1.display();
cout<<"c2=";
c2.display();
c3=c1-c2;//使用重载运算符完成复数减法
cout<<"c3=c1-c2=";
c3.display();
c3=c1+c2;//使用重载运算符完成复数加法
cout<<"c3=c1+c2=";
c3.display();
}
运算符重载的设计原则

运算符是: 建议设计为:

全部单目运算符 非静态成员函数

= () [ ] -> * 必须是非静态成员函数

+= -= *= /= %= 非静态成员函数

<<= >>= ^= |= &= 非静态成员函数

双目运算符 友元函数

>> << 必须是友元函数

虚函数 成员函数

若允许链式运算 该函数应返回本类型的引用

若定义了+ -* / % 千万别忘了定义=

对于任何形参,若仅读取值而不修改,则应设为const &;

函数的返回值的类型,可以是参数对象的类型(如+),也可以异于参数对象(如<=);

所有的赋值运算符皆可以改变左值。为满足链式运算要求,函数应返回同类型的非常引用。(记住:编译器是自左向右扫描表达式的,但却是从右向左来处理的。)

对于逻辑运算符,应返回bool类型,至少是int型;

对于自增自减运算符,可以返回本类的对象,亦可处理成返回是否继续迭代的逻辑值;

下标运算符必须是成员函数,单个形参,返回是引用;

()运算符是唯一允许带任意个参数的函数,必须设计为成员函数;

指针运算符->最神奇也最复杂,不但重载了,甚至演化为类,作用于容器,叫作迭代子。

4)虚函数

虚函数是动态绑定的技术基础。

是非静态的成员函数。

在类的声明中,在函数原型之前写virtual。

virtual只用来说明类声明中的原型,不能用在函数实现时。

具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。

本质:不是重载(overload)而是覆盖(override)。

调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类型,决定调用哪个类的函数。

虚函数的实现机制

编译器发现某类含有虚函数,则对其生成的对象悄悄地加入一个void 型的指向指针的指针vptr,并让其指向一个虚函数表vtable(其实是个指针数组),每个表项是一个虚函数名,排列次序按虚函数声明的次序排列。

在类族中,无论是基类还是派生类,都拥有各自的vptr和vtable。相同类型所生成的对象拥有相同的vtable。

派生类新增的虚函数依次排在后面。当然,派生类的vtable表项中放的是新的覆盖函数的首址。

动态多态的前提

(缺一不可)

必须有继承产生的类族;

必须是公有继承(类型兼容);

派生类的成员函数要重写该虚函数;

基类的某成员函数使用了virtual;

派生类的对象要使用指针或引用来调用该虚函数;



虚析构函数

为何需要虚析构函数?

避免析构对象不彻底。

何时需要虚析构函数?

当一个类含有虚函数时。

因为,当你打算用基类指针(或引用)删除子类对象,由于切割现象,会只释放基类部分,这会遗留内存垃圾。让基类的析构函数成为虚函数,则会彻底释放内存。

10. 局部类,内嵌类,抽象类

详见:/article/7786086.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: