C++学习(42)

来源:互联网 发布:如何把矩阵正交化 编辑:程序博客网 时间:2024/05/17 20:32

1.如果一个类中声明了纯虚函数,其派生类中没有对该函数定义,那该函数在派生类中仍为纯虚函数,凡是包含纯虚函数的类都是抽象类。

通常重载函数调用的依据是函数名、参数类型、参数个数。

 

类的静态成员是属于类的而不是属于哪个对象的,因此可以说是所有对象所共有的

 

内联函数是在编译时将目标代码插入的。

 

2.“子类”和“子类型”是不同的,替换原则只适合于"子类型"关系,而一般编程语言只是考虑了"子类"关系,

 

子类 :说明了新类是继承自父类

 

子类型 :强调的是新类具有父类一样的行为(未必是继承)。

 

那么,什么时候才应该使用继承?那就是符合子类型关系的时候,或者一般所说的”is a"关系,你必须保证新类的行为与父类完全一致!!!

在任何使用父类的场合,新类应该表现一样的行为。

 

3.下列代码共调用多少次拷贝构造函数:7

Widget f(Widget u) {  Widget v(u);  Widget w=u;  Return w;}main() {  Widget x;  Widget y=f(f(x));}

分析:y=f(f(x)) 有两层 f() ,为了说明过程,把里面的一层标明为 f_1 ,外面一层标明为 f_2 则7 次调用分别是:

x->f_1 的 uf_1 的 u->f_1 的 vf_1 的 v -> f_1 的 wf_1 的 w -> f_2 的 uf_2 的 u -> f_2 的 vf_2 的 v->f_2 的 wf_2 的 w->y

4.编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。所以建议的方式是将析构函数声明为虚函数

 

也就是delete a的时候,也会执行派生类的析构函数。

 

一个函数一旦声明为虚函数,那么不管你是否加上virtual修饰符,它在所有派生类中都成为虚函数。但是由于理解明确起见,建议的方式还是加上virtual 修饰符。

 

构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。

 

如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

 

构造原则如下:

1).如果子类没有定义构造方法,则调用父类的无参数的构造方法。

2).如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。

3).在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。

4).在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。

5).在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。

6).如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式。

 

5.分析下述程序:运行test函数会有未定义的行为。

#include<iostream>#include<cstdlib>#include<string.h>using namespace std;void test (void ) {  char *str=(char*)malloc(100);  strcpy(str,"hello");  free(str);  if(str!=NULL) {    strcpy(str,"world");    printf(str);  }}int main() {  test();  return 0;}

分析:free指针之后,再使用不保证正确性。指针释放存储空间后没有置为NULL,变成野指针。

 

6.[编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。 因此,将析构函数设为私有,类对象就无法建立在栈(静态)上了,只能在堆上(动态new)分配类对象。]

点击打开链接

 

《effective C++》条款16提到的,使用new时,有两件事发生:1).内存被分配出来(通过operator new函数);2).针对此内存调用构造函数。new还是i要调用构造函数的。而如果析构函数设为私有,编译器不会在栈空间分配内存,也就不能直接创建对象了,然而对于new,有自定义的函数来完成delete的功能。

 

7.malloc函数为C语言中的标准函数,标准中规定:在分配内存失败时会返回“NULL Pointer”空指针,而非为初始化的指针。

 

C++在分配内存失败时会抛出BAD_ALLOC异常。

 

野指针:指向垃圾内存的指针,而非空指针

 

野指针产生原因:

1).声明的指针未被初始化,指针默认值随机产生。创建指针应该将其初始化为NULL或者指向某一内存。

2).free和delete掉的指针未重置为NULL,free后的指针仍指向该内存,但该内存已变为垃圾内存。

另:空指针不指向任何实际的对象或函数,反过来说对象或函数的指针也不可能为空指针。

 

空指针与野指针的区别,空指针也就是通常指向为NULL的指针,野指针就是指向一块未知的内存区域(可以是通过malloc或new申请空间后,释放后没有将指针置为空),也有可能定义了一个指针没有初始化,由于内存空间中的值在未赋值之前是随机数,所以也有可能诞生野指针。

 

无法为内存为0的地址取地址。

 

空指针与任何对象或函数的指针值都不相等。

 

空指针可以确定不指向任何对象或函数,而未初始化指针则可能指向任何地方。

 

8.两个等价线程并发执行下列程序,a为全局变量,初始为0,假设printf、++、--操作都是原子性的,则输出肯定不是哪个?

void foo(){  if(a<=0) {    a++;}  else {    a--;  }Printf(“%d”,a);}

分析:与a有关的语句有3个:a<=0,判断语句,从内存取值,不写回内存;a++/a--,从内存取值,有对a的赋值,结果要写回内存;printf打印a,要从内存取值。

 

1)、线程P1执行完foo(),线程P2再执行foo()。结果是10。

2)、线程P1从内存取出a=0,执行判断语句后等待;线程P2从内存取出a=0,执行判断语句后等待;线程P1再执行a++,a变为1,写回内存,等待;线程P2从内存取出a=1,再执行a++,a变为2,写回内存,等待;线程P1执行printf,输出2,线程P2执行printf,输出2。结果是22。

3)、线程P1从内存取出a=0,执行判断语句后等待;线程P2从内存取出a=0,执行判断语句后等待;线程P1再执行a++,a变为1,写回内存,继续执行printf,输出1,等待;线程P2从内存取出a=1,再执行a++,a变为2,写回内存,继续执行printf,输出2。结果是12。

4)、还有种可能是00。但不会是01。

 

9. JAVA没有指针的概念,被封装起来了,而C++有;

JAVA不支持类的多继承,但支持接口多继承,C++支持类的多继承;

C++支持操作符重载,JAVA不支持;

JAVA的内存管理比C++方便,而且错误处理也比较好;

C++的速度比JAVA快,C++更适用于有运行效率要求的情况;JAVA适用于效率要求不高,但维护性要好的情况。

 

10.conststatic数据成员可以在类内初始化 也可以在类外,不能在构造函数中初始化,也不能在构造函数的初始化列表中初始化

 

static数据成员只能在类外,即类的实现文件中初始化,也不能在构造函数中初始化,不能在构造函数的初始化列表中初始化;

 

const数据成员只能在构造函数的初始化列表中初始化;

 

普通数据成员不能在类内初始化,可以在构造函数中初始化,也可以在构造函数的初始化列表中初始化。

原创粉丝点击