C++多重继承的指针问题(锦集9篇)由网友“会子”投稿提供,下面是小编为大家汇总后的C++多重继承的指针问题,仅供参考,欢迎大家阅读,希望可以帮助到有需要的朋友。
篇1:C++多重继承的指针问题
下面说说C++多重继承中关于指针的一些问题,
指针指向问题
先看下面的程序:
class Base1{public: virtual void fun1 {cout << “Base1::fun1” << endl;};};class Base2{public: virtual void fun2() {cout << “Base2::fun1” << endl;};};class Derive : public Base1, public Base2{public: virtual void fun1 () {cout << “Derive::fun1” << endl;} virtual void fun2 () {cout << “Derive::fun2” << endl;}};int main(){ Derive oD; Base1 *pB1 = (Base1*)(&oD); Base2 *pB2 = (Base2*)(&oD); cout << “&oD=” << &oD << '\n'; cout << “pB1=” << pB1 << '\n'; cout << “pB2=” << pB2 << '\n'; if (&oD == pB1) cout << “&oD == pB1” << '\n'; if (&oD == pB2) cout << “&oD == pB2” << '\n';}
我电脑上的运行结果:
首先,可以看到&oD和pB1指针指向相同的存储地址。为什么?
这是因为当我们new一个Derive类的时候,计算机给Derive类分配的空间可以分为三部分:首先是类Base1的部分,然后是Base2的部分,然后是Derive中除去Base和Base2剩余部分,如下图。
Base1Base2Derive所以&oD肯定保存的是整体的首地址,而pB1指向的是Base1的首地址,恰好也是整体的首地址,所以有&oD和pB1的值刚好相等。pB2则指向的是Base2的首地址。
可是后面为什么会有&oD == pB2呢?这是因为当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,&oD和pB2虽然指针值不等,但是他们确确实实都指向了同一个内存对象(即new Derive产生的内存对象)。<?www.2cto.com/kf/ware/vc/“ target=”_blank“ class=”keylink“>vcD4NCjxociAvPg0KPHA+PHN0cm9uZz7WuNXrwODQzdequ7vOysziPC9zdHJvbmc+PGJyIC8+DQq7ucrHyrnTw8nPw+a1xMDgo6y/tNb3uq/K/aO6PC9wPg0KPHByZSBjbGFzcz0=”brush:java;“>int main(){ Derive oD; cout << ”&oD=“ << &oD << '\n'; Base1 *pB1 = &oD; cout << ”pB1=“ << pB1 << '\n'; pB1->fun1(); cout << endl; Base2 *pB2 = (Base2*)(pB1); // 指针强行转换,没有偏移 cout << ”pB2=“ << pB2 << '\n'; pB2->fun2(); cout << endl; pB2 = dynamic_cast(pB1); // 指针动态转换,dynamic_cast帮你偏移 cout << ”pB2=“ << pB2 << '\n'; pB2->fun2(); return 0;}
猜猜执行结果:
是不是很意外,为什么pB2->fun2()的结果是Derive::fun1,
这里我们看到的是使用强制类型转换是不能把Base1类型的指针转成Base2类型的指针的,必须使用dynamic_cast的形式进制转换才奏效。
下面我们探索下为什么输出的是Derive::fun1。
我们修改Base1的定义:
class Base1{public: virtual void fun3() {cout << ”Base1::fun3“ << endl;}; virtual void fun1() {cout << ”Base1::fun1“ << endl;};};
给添加一个函数fun3,然后再次执行上面的main函数,结果如下:
我们可以发现强制转换不会成功,也不会报错,你调用Base2的fun2函数,因为强制转换不成功,所以指针仍然指向Base1,而Base1中没有fun2,所以就会自动调用声明的函数中的第一个函数。(不知道为什么会这样设计!)
上面强制将Base1转为Base2不会报错,但是不能运行处正确结果。而我们强制将Base2转为Base1呢?
int main(){ Derive *pD = new Derive(); Base2 *pB2 = pD; pB2->fun2(); Base1 *pB1 = (Base1*)(pB2); pB1->fun1(); return 0;}
这样程序执行到第6行的时候会直接奔溃。
所以:
1. C++多重继承需要慎用2. 类型转换尽量采用c++内置的类型转换函数,而不要强行转换。
篇2:c语言指针
一、数组的指针、指针数组以及指向指针的指针
考虑数组的指针的时候我们要同时考虑类型和维数这两个属性,换一句话,就是说一个数组排除在其中存储的数值,那么可以用类型和维数来位置表示他的种类。
A)一维数组
在c和c++中数组的指针就是数组的起始地址(也就第一个元素的地址),而且标准文档规定数组名代表数组的地址(这是地址数值层面的数组表示)。例如:
int a[10]; int *p;
p=&a[0]//和p=a是等价的:
因为a是数组名,所以他是该数组的地址,同时因为第一个元素为a[0],那么&a[0]也代表了该数组的地址。但是我们是不是就说一个数组名和该数组的第一个元素的&运算是一回事呢?在一维的时候当时是的,但是在高维的时候,我们要考虑到维数给数组带来的影响。
a[10]是一个数组,a是数组名,它是一个包含10个int类型的数组类型,不是一般的指针变量噢!(虽然标准文档规定在c++中从int[]到int*直接转换是可以的,在使用的时候似乎在函数的参数为指针的时候,我们将该数组名赋值没有任何异样),a代表数组的首地址,在数字层面和a[10]的地址一样。这样我们就可以使用指针变量以及a来操作这个数组了。
所以我们要注意以下问题:
(1) p[i]和a[i]都是代表该数组的第i+1个元素;
(2) p+i和a+i代表了第i+1个元素的地址,所以我们也可以使用 *(p+I)和*(a+I)来引用对象元素;
(3)p+1不是对于指针数量上加一,而是表示从当前的位置跳过当前指针指向类型长度的空间,对于win32的int为4byte;
B)多维数组
对于二维数组a[4][6];由于数组名代表数组的起始地址,所以a(第一层)和第一个元素a[0][0]地址的数字是相同的,但是意义却是不同的。对于该数组我们可以理解为:a的一维数组(第一层),它有四个元素a[0]、a[1]、a[2]、a[3](第二层),而每个元素又含有6个元素a[0][0],a[0][1],a[0][2],a[0][3],a[0][4],a[0][5](第三层),…到此我们终于访问到了每个元素了,这个过程我们经历了:a->a[0]->a[0][0];
整体来讲:a是一个4行5列的二维数组,a表示它指向的数组的首地址(第一个元素地址&a[0]),同时a[0]指向一行,它是这个行的名字(和该行的第一个元素的首地址相同(第一个元素为地址&a[0][0]))。所以从数字角度说:a、a[0]、&a[0][0]是相同的,但是他们所处的层次是不同的。
既然a代表二维数组,那么a+i就表示它的第i+1个元素*(a+i)的地址,而在二维数组中
*(a+i)又指向一个数组,*(a+i)+j表示这个数组的第j+1个元素的地址,所以要访问这个元素可以使用 *(*(a+i)+j)(也就是a[i][j])。
他们的示意图为(虚线代表不是实际存在的):
对照这个图,如下的一些说法都是正确的(对于a[4][6]):
a是一个数组类型,*a指向一个数组;
a+i指向一个数组;
a、*a和&a[0][0]数值相同;
a[i]+j和*(a+i)+j是同一个概念;
总结一下就是:我们对于二维指针a,他指向数组a[0,1,2,3],使用*,可以使他降级到第二层次,这样*a就指向了第一个真正的数组。对于其他的情况我们也可以采用相同的方式,对于其他维数和类型的数组我们可以采用相类似的思想,
说到指向数组的指针,我们还可以声明一个指针变量让它指向一个数组。例如:
int (*p)[5];
这时p就是一个指针,要指向一个含有5个int类型元素的数组,指向其他的就会出现问题。
这个时候我们可以使用上面的什么东西来初始化呢?
我们可以使用*a,*(a+1),a[2]等。
原因很简单:我们在一个二维的数组中,那么表达方式有上面的相互类似的意义呢?只有 *a,*(a+1),a[2]等,
C)指针数组
一个指针数组是指一个数组中的每个元素都是一个指针,例如:
int *p[10];//而不能是int (*p)[10]
或者
char *p[10];
此时p是一个指针(数值上和&p[0]一样);
在前面有int t[10];
int * pt=t;//使用pt指向t
那么这里我们用什么指向int *t[10]中的t呢?我们要使用一个指针的指针:
int **pt=t;
这是因为:在int *t[10]中,每个元素是指针,那么同时t又指向这个数组,数组上和&t[0]相同,也就是指向t[0],指向一个指针变量,可以说是一个指针的指针了,所以自然要用
int **pt;
D)指针的指针
一个指针变量内部可以存储一个值,这个值是另外一个对象的地址,所以我们说一个指针变量可以指向一个普通变量,同样这个指针变量也有一个地址,也就是说有一个东西可以指向这个指针变量,然后再通过这个指针变量指向这个对象。那么如何来指向这个指针变量呢?由于指针变量本身已经是一个指针了(右值),那么我们这里就不能用一般的指针了,需要在指针上体现出来这些特点,我们需要定义指针的指针(二重指针)。
int *p1=&i; int**p2=&p1;
综合以上的所有点,下面是我们常常看到一些匹配(也是经常出错的地方):
int a[3],b[2][3],c,*d[3]; void fun1(int *p); void fun2(int (*p)[3]); void fun3(int **p); void fun4(int p[3]); void fun5(int p[]); void fun6(int p[2][3]); void fun7(int (&p)[3]);
函数 不会产生编译时刻的可能值(但逻辑上不一定都对)
函数
不会产生编译时刻的可能值(但逻辑上不一定都对)
fun1
a, &a[i], *b ,b[i],&b[i][j] ,&c ,d[i]
fun2
b,b+i,
fun3
d
fun4
a, &a[i], *b ,b[i],&b[i][j] ,&c ,d[i]
fun5
a, &a[i], *b ,b[i],&b[i][j] ,&c ,d[i]
fun6
b
篇3:C语言指针
指针变量是包含内存地址的变量,它指向内存中的一块区域,通过指针的值,可以间接访问到相应的内存单元的数据,并做相应的修改,
1、指针的定义和简单使用
定义一个指针变量和定义一般的变量类似,只需在变量名前面加一个“*”。对一个指针变量赋值可以用取地址符&来获取到一个变量的地址,如果要获得指针指向的内存区域的数据,用解参考运算符*(也称为间接运算符,它返回其操作数指向的对象的值)。指针的值为NULL(NULL是stdio.h中定义的符号变量,实际上是0)说明其不指向任何的内存单元,0是唯一直接可以赋值给指针变量的整数值。实际上,*和&是互补的,当两个运算符连续应用于一个指针变量时,无论顺序如何,运算结果相同。同时可以用printf中的格式化字符串%p来输出指针变量的值,下面是一个简单的程序。
[cpp]
#include
int main
{
int a;
a=9;
//定义并初始化一个指针,命名就可以看出
int *aPtr=NULL;
//将指针指向变量a
aPtr=&a;
printf(”The address of a is %p“
”\nThe value of aPtr is %p“,&a,aPtr);
printf(”\n\nThe value of a is %d“
”\nThe value of *aPtr is %d“,a,*aPtr);
printf(”\n\nShowing that * and & are complements of “
”each other\n&*aPtr = %p“
”\n*&aPtr = %p\n“,&*aPtr,*&aPtr);
return 0;
}
2、用指针做函数的参数
2.1 通过指针实现的引用传递
程序设计语言的参数传递方式,大致分两种:值传递和引用传递。C语言中没有引用传递,但是C语言通过指针间接实现了引用传递。通过用指针变量作为函数的参数,可以传递变量的地址(只需要在变量前面加上&运算符就可以),这样,用该地址就可以访问到主调函数中的该变量的内存地址,并可以进行相应的修改。这样,在函数执行完毕之后,修改仍然可以得到保留。
2.2 const
const限定符可以告诉编译器特定的变量的值是不能被修改的。如果想确保函数不会修改传递进来的参数值,应该将参数声明为const。这样对于C语言中用指针实现的引用传递,有四种情况:指向非常量数据的非常量指针(int *aPtr),指向非常量数据的常量指针(int *const aPtr),指向常量数据的非常量指针(const int *aPtr)和指向常量数据的常量指针(const int * const aPtr)。简单的说,就是指针变量自身和指针指向的变量都有可能是const,这样就产生了四种情况,这四种情况提供了四种不同的访问权限,下面分别解释。
指向非常量数据的非常量指针(int *aPtr):指针的值本身和指针指向变量的值都可以在函数中被修改。
指向非常量数据的常量指针(int *const aPtr):指针的值不能被修改,但是指针指向的变量的值可以被修改。
指向常量数据的非常量指针(const int *aPtr):指针指向的值不能被修改,但是指针本身的值可以被修改。
指向常量数据的常量指针(const int * const aPtr):指针本身和指针指向变量的值都不能被修改。
3、sizeof和指针运算
3.1 sizeof
sizeof是C语言中特殊的一元运算符,可以应用在变量名称、数据类型和常量之前,它在程序编译期间以字节为单位来确定数组或其他数据类型的大小。当应用于数组时,sizeof返回数组中的字节总数。如float a[20],sizeof(a)的值应该是4*20,80。当然,如果想获得数组的大小可以采用sizeof(a)/sizeof(float)。
3.2 指针运算
实际上,指针变量可以进行的算术操作是有限的:递增,递减,将指针和整数相加,从指针中减去一个整数或者一个指针减去另一个指针,
需要注意的是,对于指针的算术运算,其单位长度并不是一般意义上的1,而是sizeof(TYPE)。这样,如果float a[14]; float *aPtr=a;(或者int *aPtr=&a[0]); aPtr++;这样aPtr应该指向的是数组a的第二个元素,也就是a[1],这里的单位长度就是sizeof(float)。同样地,如果aPtr=aPtr+5;,这样aPtr又指向了数组a的第6个元素。如果aPtr-a,这样可以得到两个指针之间的元素间隔个数。应该是6。
进行指针运算要注意:
(1)如果将一个指针的值赋给另外一个指针,那么这两个指针的类型必须相同,否则应该用类型转换运算符进行类型转换。但是,有一个例外就是指向void类型的指针,它是通用指针,可以代表任何指针类型。因此,所有指针类型都可以赋值给void指针,而void指针也可以赋值给任何类型的指针,而不需要任何类型转换运算符。但是,void指针不能解参考,编译器知道指向int类型的指针引用的是32位计算机上的4个字节内存,但指向void的指针仅包含未知数据类型的内存位置,也就是说,编译器不知道指针所引用的字节数。编译器必须知道数据类型,才能确定所引用的字节数。
(2)除非两个指针变量都指向的是一个数组中的元素,否则对它们相减的结果没有任何意义,因为我们不能假设两个变量在内存中是连续的。
4、指针和数组
4.1 数组和指针的共性
实际上,数组名称的本质是一个常量指针。因此,int a[6]; int *aPtr;定义一个数组和指针之后,通过a[3],*(a+3)和*(aPtr+3)都可以访问到数组的第四个元素的值。但是区别在于,aPtr=aPtr+3;这样aPtr就指向了a数组的第四个元素,但是,不能a=a+3;,因为a是一个数组名,它是一个常量指针它的值不能被修改,更加具体地说,它应该是一个指向非常量数据的常量指针。
4.2 指针数组
数组元素也可以是指针类型,指针数组常见的用途就是构成由字符串组成的数组,简单地说就是字符串数组,数组中的每一个元素都是字符串。下面是一个例子,const限定符说明不能修改每个元素指针所指向的字符串。
[cpp]
#include
int main()
{
const char *suit[4]={”Hearts“,”Diamonds“,”Clubs“,”Spades“};
int i;
for(i=0;i<4;i++)
printf(”sizeof pointer to \“%s\” :%d\n“,suit[i],sizeof(suit[i]));
printf(”\nsizeof suit:%d\n“,sizeof(suit));
return 0;
}
运行结果如下:
从这个例子中,可以看出const char *suit[4]={”Hearts“,”Diamonds“,”Clubs“,”Spades“};定义了一个指针数组,但是,其中的每个元素只是一个指针,而不是数组名(如果是数组名的话,sizeof的结果不应该都是4)。这样定义字符串数组可以节省空间。
5、函数指针
和数组名实际上就是数组第一个元素在内存中的地址类似,函数迷你实际上就是执行函数任务的代码在内存中的起始地址。函数指针包含函数在内存中的地址,可以传递给函数、从函数返回、存储在数组中或者是赋值给其它的函数指针,下面是两个函数指针的例子。
(1) 用函数指针实现升序/降序排序
[cpp]
#include
#define SIZE 10
int ascending(int a,int b)
{
return a>b;
}
int descending(int a,int b)
{
return a
}
void swap(int *aPtr,int *bPtr)
{
int temp=*aPtr;
*aPtr=*bPtr;
*bPtr=temp;
}
篇4:小小的C语言问题指针数组赋值关于指针和数组。
先上代码吧:
?
#include
#include
using namespace std ;
int replacefun(char* str, char c1, char c2);
int main(void)
{
char * p = ”I love you China, do you love me?“; // 用指针的形式定义一个字符数组
int m = 0;
m = replacefun(p, 'o', 'c');
cout << m << endl;
return 0;
}
int replacefun(char* str, char c1, char c2)
{
int num = 0;
while (*str != '\0')
{
if (*str == c1)
{
*str = c2; // 这句话调试的时候内存报错
num++;
}
str++;
}
return num;
}
这个程序实现的目的的是替换指定的字符,在编译的时候没有任何错误,但是在运行的时候,程序意外终止,
小小的C语言问题指针数组赋值关于指针和数组,
电脑资料
《小小的C语言问题指针数组赋值关于指针和数组。》()。。
于是,启动利器,单步调试。。。跳进函数后,发现在*str到达'o'之前一切正常,不过。。到了'o'之后,结果说内存错误,无法赋值。
经过多方查找资料和询问,得知:
char *p=”abcde“;的时候,字符串是不可以修改的!
而char p[]=”abcde“;的时候,字符串是可以修改的!
同一个字符串,用指针定义的时候在常量区,而用数组定义的时候在栈中。
就是刚刚上面的那个
I love you China, do you love me?
是一个常量。
据大婶说:
”I love you China, do you love me?“
在存放在.rodata段,该段是只读的,当你强行做修改的时候,当然要报错喽。
而定义为数组的形式之后:就把这个字符串拷贝进数组了,对于数组中的内容,可以随便修改呀. 原来的字符串还是不变的。。。
以上总结,涨姿势了。
继续C++。
篇5:说说C++多重继承
尽管大多数应用程序都使用单个基类的公用继承,但有些时候单继承是不够用的,因为可能无法为问题域建模或对模型带来不必要的复杂性,在这种情况下,多重继承可以更直接地为应用程序建模。
一、基本概念
多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其父类的属性。
class ZooAnimal{
};
class Bear : public ZooAnimal{
};
class Endangered{
};
class Panda : public Bear, public Endangered{
};
注意:
(1)与单继承一样,只有在定义之后,类才可以用作多重继承的基类。
(2)对于类可以继承的基类的数目,没有语言强加的限制,但在一个给定派生列表中,一个基类只能出现一次。
1、多重继承的派生类从每个基类中继承状态
Panda ying_yang(”ying_yang“);
对象ying_yang包含一个Bear子类对象、一个Endangered子类对象以及Panda类中声明的非static数据成员。如下图所示:
2、派生类构造函数初始化所有基类
派生类构造函数可以早构造函数初始化式中给零个或多个基类传递值。
Panda::Panda(string name, bool onExhibit)
: Bear(name, onExhibit, ”Panda“),
Endangered(Endangered::critical){}
构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中的出现次序调用。对于Panda,基类初始化次序是:
(1)ZooAnimal。
(2)Bear,第一个直接基类。
(3)Endangered,第二个直接基类,它本身没有基类。
(4)Panda,初始化本身成员,然后运行它的构造函数的函数体。
注意:构造函数调用次序既不受构造函数初始化列表中出现的基类的影响,也不受基类在构造函数初始化列表中的出现次序的影响。例如:
Panda::Panda : Endangered(Endangered::critical){}
这个构造函数将隐式调用Bear的默认构造函数,尽管它不出现在构造函数初始化列表中,但仍然在Endangered类构造函数之前调用。
3、析构的次序
按照构造函数运行的逆序调用析构函数。Panda、Endangered、Bear,ZooAnimal。
二、转换与多个基类
单个基类情况下,派生类的指针或引用可自动转换为基类的指针或引用。对于多重继承,派生类的指针或引用可以转换为其任意基类的指针或引用。
注意:在多重继承情况下,遇到二义性转换的可能性更大。编译器不会试图根据派生类转换来区别基类间的转换,转换到每个基类都一样好。例如:
void print(const Bear&);
void print(const Endangered&);
通过Panda对象调用print时,会导致一个编译时错误。
1、基于指针或引用类型的查找
与单继承一样,用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员。当一个类派生于多个基类的时候,那些基类之间没有隐含的关系,不允许使用一个基类的指针访问其他基类的成员。例如:
class ZooAnimal
{
public:
virtual void print(){}
virtual ~ZooAnimal(){}
};
class Bear : public ZooAnimal
{
public:
virtual void print()
{
cout << ”I am Bear“ << endl;
}
virtual void toes(){}
};
class Endangered
{
public:
virtual void print(){}
virtual void highlight()
{
cout << ”I am Endangered.highlight“ << endl;
}
virtual ~Endangered(){}
};
class Panda : public Bear, public Endangered
{
public:
virtual void print()
{
cout << ”I am Panda“ << endl;
}
virtual void highlight()
{
cout << ”I am Panda.highlight“ << endl;
}
virtual void toes(){}
virtual void cuddle(){}
virtual ~Panda()
{
cout << ”Goodby Panda“ << endl;
}
};
当有如下调用发生时:
int main()
{
Bear *pb = new Panda();
pb->print(); //ok: Panda::print
// pb->cuddle(); //error: not part of Bear interface
// pb->highlight(); //error: not part of Bear interface
delete pb; //Panda::~Panda
Endangered *pe = new Panda();
pe->print(); //ok: Panda::print
// pe->toes(); //error: not part of Endangered interface
// pe->cuddle(); //error: not part of Endangered interface
pe->highlight(); //ok: Panda::highlight
delete pe; //Panda::~Panda
return 0;
}
2、确定使用哪个虚析构函数
我们假定所有根基类都将它们的析构函数定义为虚函数,那么通过下面几种删除指针方法,虚析构函数处理都是一致的。
delete pz; //pz is a ZooAnimal*
delete pb; //pb is a Bear*
delete pp; //pp is a Panda*
delete pe; //pe is a Endangered*
假定上面四个指针都指向Panda对象,则每种情况发生完全相同的析构函数调用次序,即与构造次序是逆序的:通过虚机制调用Panda析构函数,再依次调用Endangered、Bear,ZooAnimal的析构函数。
三、多重继承派生类的复制控制
多重继承的派生类使用基类自己的复制构造函数、赋值操作符,析构函数隐式构造、赋值或撤销每个基类。下面我们做几个小实验:
1 class ZooAnimal
2 {
3 public:
4 ZooAnimal()
5 {
6 cout << ”I am ZooAnimal default constructor“ << endl;
7 }
8 ZooAnimal(const ZooAnimal&)
9 {
10 cout << ”I am ZooAnimal copy constructor“ << endl;
11 }
12 virtual ~ZooAnimal()
13 {
14 cout << ”I am ZooAnimal destructor“ << endl;
15 }
16 ZooAnimal& perator=(const ZooAnimal&)
17 {
18 cout << ”I am ZooAnimal copy perator=“ << endl;
19
20 return *this;
21 }
22 };
23 class Bear : public ZooAnimal
24 {
25 public:
26 Bear()
27 {
28 cout << ”I am Bear default constructor“ << endl;
29 }
30 Bear(const Bear&)
31 {
32 cout << ”I am Bear copy constructor“ << endl;
33 }
34 virtual ~Bear()
35 {
36 cout << ”I am Bear destructor“ << endl;
37 }
38 Bear& perator=(const Bear&)
39 {
40 cout << ”I am Bear copy perator=“ << endl;
41
42 return *this;
43 }
44 };
45 class Endangered
46 {
47 public:
48 Endangered()
49 {
50 cout << ”I am Endangered default constructor“ << endl;
51 }
52 Endangered(const Endangered&)
53 {
54 cout << ”I am Endangered copy constructor“ << endl;
55 }
56 virtual ~Endangered()
57 {
58 cout << ”I am Endangered destructor“ << endl;
59 }
60 Endangered& perator=(const Endangered&)
61 {
62 cout << ”I am Endangered copy perator=“ << endl;
63
64 return *this;
65 }
66 };
67 class Panda : public Bear, public Endangered
68 {
69 public:
70 Panda()
71 {
72 cout << ”I am Panda default constructor“ << endl;
73 }
74 Panda(const Panda&)
75 {
76 cout << ”I am Panda copy constructor“ << endl;
77 }
78 virtual ~Panda()
79 {
80 cout << ”I am Panda destructor“ << endl;
81 }
82 Panda& perator=(const Panda&)
83 {
84 cout << ”I am Panda copy perator=“ << endl;
85
86 return *this;
87 }
88 };
还是前面的类,只不过我将没有必要的虚函数去掉了,
下面我执行以下操作:
int main()
{
cout << ”TEST 1“ << endl;
Panda ying_ying;
cout << endl << endl;
cout << ”TEST 2“ << endl;
Panda zing_zing = ying_ying;
cout << endl << endl;
cout << ”TEST 3“ << endl;
zing_zing = ying_ying;
cout << endl << endl;
return 0;
}
这个结果是毫无疑问的,先调用基类构造函数,再调用派生类。
首先调用默认构造函数构造一个zing_zing对象,然后调用拷贝构造函数,将ying_ying拷贝至zing_zing。注意:这里用的是拷贝构造函数,而不是赋值操作符,那什么时候用赋值操作符呢?我们接着看TEST3的结果:
这种情况才调用赋值操作符:就是两个对象都已经分配内存后,再进行赋值。这里有个疑问,基类也定义了operator=了,为什么不调用基类的operator=呢?我们将Panda类的operator=注释掉,重新来做TEST3,好玩的结果出现了:
Panda的合成赋值操作符调用了两个基类的operator=。
我们得出以下结论:如果派生类定义了自己的复制构造函数或赋值操作符,则负责复制(赋值)所有的基类子部分,而不再调用基类相应函数。只有派生类使用合成版本的复制构造函数或赋值操作符,才自动调用基类部分相应的函数。
最后我们来看一下析构函数的表现:
析构函数的行为是符合我们预期的,这里有一点我没有体现出来就是zing_zing是ying_ying之后定义的对象,所以zing_zing的构造函数先执行(前4行),后4行代表ying_ying构造函数的执行。如果具有多个基类的类定义了自己的析构函数,则该析构函数只负责清除派生类。
四、多重继承下的类作用域
在多重继承下,多个基类作用域可以包围派生类作用域。查找时,同时检查所有基类继承子树,例如:并行查找Endangered和Bear/ ZooAnimal子树。如果在多个子树上找到该名字,那个名字必须显式指定使用哪个基类。否则,该名字的使用是二义性的。
例如:Endangered类和Bear类都有print函数,则ying_ying.print()将导致编译时错误。
注意:
(1)Panda类的派生导致有两个名为print的成员是合法的。派生只是导致潜在的二义性,如果没有Panda对象调用print,就可避免这个二义性。你可以Bear::print或Endangered::print来调用。
(2)当然,如果只在一个基类子树上找到声明是不会出错的。
下面仍然有个小实验要做:
class ZooAnimal
{
public:
//void print(int x){}
};
class Bear : public ZooAnimal
{
public:
void print(int x){}
};
class Endangered
{
public:
void print(){}
};
class Panda : public Bear, public Endangered
{
public:
};
TEST1:将两个基类Bear和Endangered两个print的形参表设为不同。
TEST2:将Bear中的print去掉,在ZooAnimal中增加print。
TEST3:将Endangered中print设置为private访问。
以上三种情况下,当我这样调用ying_ying.print()或ying_ying.print(1)时,都显示编译时错误(二义性)。
我们的得出这样的结论:名字查找的过程是这样的,首先编译器找到一个匹配的声明(找到两个匹配的声明,这导致二义性),然后编译器才确定所找到的声明是否合法。
所以说,当我们调用这样的函数时,应该这样ying_ying.Bear::print()。
篇6:笔试题多重继承
笔试题(多重继承)
1. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数,
KMP算法效率最好,时间复杂度是O(n+m)。
2. 多重继承的内存分配问题:
比如有class A : public class B, public class C {}
那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的'实现其细节可能不同。
如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。
可以参考《深入探索C++对象模型》,或者:
3. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)
struct node { char val; node* next;}
bool check(const node* head) {} //return false : 无环;true: 有环
一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):
bool check(const node* head)
{
if(head==NULL) return false;
node *low=head, *fast=head->next;
while(fast!=NULL && fast->next!=NULL)
{
low=low->next;
fast=fast->next->next;
if(low==fast) return true;
}
return false;
}
篇7:c指针笔试题
1. 变量的指针,其含义是指该变量的_________.
a)值 b)地址
c)名 d)一个标志
2.若有语句int *point,a=4;和point=&a;下面均代表地址的一组选项是_____. a)a,point,*&a b)&*a,&a,*point
c)*&point,*point,&a d)&a,&*point ,point
3.若有说明;int *p,m=5,n;以下正确的程序段的是________.
a)p=&n; b)p=&n;
scanf(”%d“,&p); scanf(”%d“,*p);
c)scanf(”%d“,&n); d)p=&n;
*p=n; *p=m;
4. 以下程序中调用scanf函数给变量a输入数值的方法是错误的,其错误原因是________.
main
{
int *p,*q,a,b;
p=&a;
printf(“input a:”);
scanf(“%d”,*p);
……
}
a)*p表示的是指针变量p的地址
b)*p表示的是变量a的值,而不是变量a的地址
c)*p表示的是指针变量p的值
d)*p只能用来说明p是一个指针变量
5. 已有变量定义和函数调用语句:int a=25; print_value(&a); 下面函数的正确输出结果是________.
void print_value(int )
{ printf(“%d\n”,++);}
a)23 b)24 c)25 d)26
6.若有说明:long *p,a;则不能通过scanf语句正确给输入项读入数据的程序段是
A) *p=&a; scanf(”%ld“,p);
B) p=(long *)malloc(8); scanf(”%ld“,p);
C) scanf(”%ld“,p=&a);
D) scanf(”%ld“,&a);
7.有以下程序
#include
main
{ int m=1,n=2,*p=&m,*q=&n,*r;
r=p;p=q;q=r;
printf(”%d,%d,%d,%d\n“,m,n,*p,*q); }
程序运行后的输出结果是
A)1,2,1,2
C)2,1,2,1
篇8:c指针笔试题
1. 有以下程序
main { int a=1, b=3, c=5; int *p1=&a, *p2=&b, *p=&c; *p =*p1*(*p2); printf(”%d\n“,c); }
执行后的输出结果是
A)1
2. 有以下程序
main
{ int a,k=4,m=4,*p1=&k,*p2=&m;
a=p1==&m;
printf(”%d\n“,a);
}
程序运行后的输出结果是
A)4
B)1 C)0 D)运行时出错,无定值 B)2 C)3 D)4 B)1,2,2,1 D)2,1,1,2
3. 在16位编译系统上,若有定义int a={10,20,30}, *p=a;,当执行p++;后,下列说法错误的是
A)p向高地址移了一个字节
C)p向高地址移了两个字节
4.有以下程序段
int a[10]={1,2,3,4,5,6,7,8,9,10},*p=&a[3],b;
b=p[5];
b中的值是
A)5 B)6 C)8 D)9
5.若有以下定义,则对a数组元素的正确引用是_________.
int a[5],*p=a;
a)*&a[5] b)a+2 c)*(p+5) d)*(a+2)
6.若有以下定义,则p+5表示_______.
int a[10],*p=a;
a)元素a[5]的地址 b)元素a[5]的值
c)元素a[6]的地址 d)元素a[6]的值
7.设已有定义: int a[10]={15,12,7,31,47,20,16,28,13,19},*p; 下列语句中正确的是
A) for(p=a;a<(p+10);a++);
B) for(p=a;p<(a+10);p++);
C) for(p=a,a=a+10;p
D) for(p=a;a
篇9:c指针笔试题
1.有以下程序段
#include
int main
{ int x = {10, 20, 30};
int *px = x;
printf(”%d,“, ++*px); printf(”%d,“, *px);
px = x;
printf(”%d,“, (*px)++); printf(”%d,“, *px);
px = x;
printf(”%d,“, *px++); printf(”%d,“, *px);
px = x;
printf(”%d,“, *++px); printf(”%d\n“, *px);
return 0; B)p向高地址移了一个存储单元 D)p与a+1等价
}
程序运行后的输出结果是( )
A)11,11,11,12,12,20,20,20 B)20,10,11,10,11,10,11,10
C)11,11,11,12,12,13,20,20 D)20,10,11,20,11,12,20,20
2.设有如下定义:
int arr={6,7,8,9,10};
int *ptr;
ptr=arr;
*(ptr+2)+=2;
printf (”%d,%d\n“,*ptr,*(ptr+2));
则程序段的输出结果为
A)8,10 B)6,8 C)7,9 D)6,10
3.若有定义:int a={2,4,6,8,10,12},*p=a;则*(p+1)的值是______. *(a+5)的值是_________.
4.若有以下说明和语句,int c[4][5],(*p)[5];p=c;能正确引用c数组元素的是______.
A) p+1 B) *(p+3) C) *(p+1)+3 D) *(p[0]+2)
5.若有定义:int a[2][3],则对a数组的第i行j列元素地址的正确引用为______. a)*(a[i]+j) b)(a+i) c)*(a+j) d)a[i]+j
6.若有以下定义:int a[2][3]={2,4,6,8,10,12};则a[1][0]的值是_____. *(*(a+1)+0)的值是________.
7.有以下定义
char a[10],*b=a;
不能给数组a输入字符串的语句是
A)gets(a) B)gets(a[0]) C)gets(&a[0]); D)gets(b);
8.下面程序段的运行结果是_________.
char *s=”abcde“;
s+=2;printf(”%d“,s);
a)cde b)字符'c' c)字符'c'的地址 d)无确定的输出结果
9.以下程序段中,不能正确赋字符串(编译时系统会提示错误)的是
A) char s[10]=”abcdefg“; B) char t=”abcdefg“,*s=t;
C) char s[10];s=”abcdefg“; D) char s[10];strcpy(s,”abcdefg“);
10.设已有定义: char *st=”how are you"; 下列程序段中正确的是
A) char a[11], *p; strcpy(p=a+1,&st[4]);
B) char a[11]; strcpy(++a, st);
C) char a[11]; strcpy(a, st);
D) char a, *p; strcpy(p=&a[1],st+2);
★ C笔试题及答案
★ C++笔试经验
【C++多重继承的指针问题(锦集9篇)】相关文章:
暑期关于学习java实习报告2023-01-16
常见华为面试题2022-12-30
羊城晚报校招笔试题2023-06-22
PowerDesign9.5+ 中的GTL编程 解决大问题数据库教程2023-08-22
C语言中表达式和表达式语句的区别2022-11-08
面向对象的知识管理系统分析方法的探讨2023-09-06
上海万国商业网笔试题2022-12-23
java个人学习总结22022-04-30
摩托罗拉销售员笔试题目2023-07-21
阿里巴巴笔试题统计2022-05-06