C++学习(25)

1.132位系统下任何类型指针的长度都是4个字节;

2)指针的数据类型声明的是指针实际指向内容的数据类型;

3)野指针是指向未分配或者已释放的内存地址;

4)野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。 


“野指针”不是NULL指针,是指向“垃圾”内存的指针。野指针的成因主要有两种: 

1)、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。 

2)、指针pfree或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看freedelete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。

 

free之后的指针仍然指向原来的堆地址,即仍然可以继续使用,但很危险。因为操作系统已经认为这块内存可以使用,它会毫不考虑的将他分配给其他程序,于是你下次不小心使用到该指针(野指针)时,如果操作系统及时制止了这种行为,报错(非法操作),然后将你的程序杀掉,给你很容易改正错误的机会,这还算比较好的结果!如果操作系统没有制止这种行为,那么产生的后果可就说不准了,说不定整个操作系统会崩溃,那么你再来改正这个错误,就不容易发现咯!所以,最好free了以后再置空,即令指针 = NULL;,表示本程序已经放弃再使用该指针。

 

2类的大小只与成员变量(非static数据成员变量)和虚函数指针有关,还要考虑到对齐.

那么类A的大小等于4个字节 + 4个字节(考虑对齐) + 4个字节(指向虚函数的指针)=12字节;

B的大小就是等于类A的大小12个字节.

因为在基类中存在虚函数时,派生类会继承基类的虚函数,因此派生类中不再增加虚函数的存储空间(因为所有的虚函数共享一块内存区域),而仅仅需要考虑派生类中添加进来的非static数据成员的内存空间大小。所以类B大小为12B

 

3. 派生类强制转化成基类(dynamic_cast)会做相应成员变量和函数的裁剪,仅能访问从基类继承来的部分成员;

 

4.一个C++程序是由一个或多个函数所组成,即使是最简单的程序,也必须有一个 main 函数。该函数是程序执行的起点和 终点 。C++中,函数不允许嵌套定义,允许嵌套调用。


5.c++中,函数名实际上是一个指针,它指向函数入口。

 

6. 运行Test函数后会有“输出乱码”的结果。

char *getmemory(void) {
              char p[]="helloworld";
              return p;
}
void test(void) {
       char *str=NULL;
       str=getmemory();
       printf(str);
}

分析:getmemory()返回的指针,是内部变量,调用之后会被回收。所以输出是不确定的。

假如修改为
char*getmemory(void)
{
char*p=”helloworld”;
returnp;
}

就可以输出"helloworld"啦!


返回“字符串常量的指针”和“返回数组名”的区别在于,一个返回静态数据区的地址,一个返回栈内存(动态数据区)的地址。

 

7.类的大小只与成员变量(非static数据成员变量)和虚函数指针有关,还要考虑对齐。虚函数有一个指向虚函数列表的指针,无论有多少个虚函数都是占用一个字节的大小。

#include<iostream>
#include<string.h>
using namespacestd;
 
class A {
       public :
              int fun1();
              virtual void fun2();
       private:
              int _a;
};
 
int main() {
       cout<<sizeof(A)<<endl;
}

分析:虚函数和private里的int各占4字节,一共8字节。类的函数是该类所有实例共享的,调用时通过隐藏的this指针和类的实例相关联。函数代码编译后根本就不在类实例中,所以不占实例空间

 

8. 

#include<iostream>
#include<string.h>
using namespacestd;
void fun(char*p,int n) {
       char b[6]="abcde";
       int i;
       for(i=0,p=b;i<n;i++)
              p[i]=b[i];
      
}
int main() {
       char a[6]="ABCDE";
       fun(a,5);
       printf("%s\n",a);
}
本题考查数组名作为函数参数, 执行f函数时, 传进去的a 指针被重新指向了b , 所以原本a 数组的地址内容不变,所以输出结果为ABCDE

 

如果将上述代码的p=b;去掉的话,最终打印结果改变。

#include<iostream>
#include<string.h>
using namespacestd;
void fun(char*p,int n) {
       char b[6]="abcde";
       int i;
       for(i=0,p=b;i<n;i++)
              p[i]=b[i];
      
}
int main() {
       char a[6]="ABCDE";
       fun(a,5);
       printf("%s\n",a);
}

分析:如果没有p=b;那么p就是指向ap[i]=a[i]改变的就是a。最终打印结果为abcde

 

9. 1所谓虚函数就是多态情况下只执行一个,而从继承的概念来讲,总是要先构造父类对象,然后才能是子类对象,如果构造函数设为虚函数,那么当你在构造父类的构造函数时就不得不显示的调用构造,还有一个原因就是为了防错,试想如果你在子类中一不小心重写了个跟父类构造函数一样的函数,那么你的父类的构造函数将被覆盖,也即不能完成父类的构造.就会出错. 在构造函数不要调用虚函数


2)在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。


3在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。

 


构造函数不能声明为虚函数的原因是:

1构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。

 

2虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。

 

先析构子类再析构父类,如果父类析构函数有虚函数,会导致调用子类的已经析构的内容。

先构造父亲类再构造子类,如果父类构造函数有虚函数,会导致调用子类还没构造的内容。

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值