c++备忘2

来源:互联网 发布:逍遥游2.4软件下载 编辑:程序博客网 时间:2024/06/05 16:52

1. 操作符重载

调用方法

total=coding.operator+(fixing)

等价与total=coding+fixing

 

total=coding+fixing+keeping

等价与

total=coding.operator+(fixing.operator+(keeping))

 

2 以下操作符只能通过成员函数进行重载

=

()

[]

->

 

3.为什么需要友元函数

total=2+fixing

等价与total=2.operator+(fixing)  错误

因此友元实现

friend Code operator+(int a, Code B);

友元参数有顺序

虽然在类中声明,但是不是成员函数,但是权限和成员函数一致

定义友元函数时不需要域操作符,因为不是成员函数

只能选择一种方法,不然编译器因为二义性出错

 

4. c++不自动转换不兼容的类型。

但是可以强制转换,int *p=(int *)10;

 

5. Code code;

code = 22;

code=Code(22);

code=(Code)22;

这是一种隐式转换。

前提,需要一个接受一个参数的构造函数从而形成转换函数才能使用上面的隐式转换,如果还有其他参数,必须有默认值。

Code(int a);

这种隐式转换可以关闭,方法

explicit Code(int a);

 

6. 对于5,如果

Code code(22);

int result =code;

这时候需要转换函数。

例如定义成员函数

operator int()const;//返回int

operator double()const;//返回double

 

7. 构造函数使用new,则析构函数用delete,构造函数用new[],则析构函数用delete[]

 

8.

void callme2(StringBad sb){};

StringBad s1;

callme2(s1);

这里调用函数时将s1传递给sb,变为

StringBad sb = s1;

这里涉及到拷贝构造函数及赋值函数。

由于这里是新对象的声明,因此会产生临时对象,然后赋值给sb,后面临时对象将会被析构。

从而导致临时对象的堆内存对象被析构,由于这里没有实现拷贝构造函数的深拷贝,因此只是拷贝了堆内存的指针。

因此后面使用该内存将会出错。

 

9.

带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。

Code(int value = 0){s = value;}

但是不能提供太多产生二义性的函数

 

10  静态成员函数不能使用this指针,因为静态成员函数不属于对象,而this指针是某个具体的对象。

只能调用静态成员,因为非静态成员都属于某一个具体的对象。

 

11 如果返回的对象为调用函数的局部变量,则不能返回引用,因为局部对象会被析构。

 

12

Code *code = new Code;

*code为对象本身,code为对象指针

 

13 关于定位new运算符用于对象

char *buffer = new char[20];

Code *p1 = new (buffer)Code;

Code *p2 = new (buffer)Code;

第二次分配时,如果类动态为其成员分配内存,则会有问题。

其次,调用delete[] buffer时,不会调用Code的析构函数。

一般如果要使用,需要保证内存不重叠

p2 = new (buffer+sizeof(Code))Code;

 

14. 类的成员函数不占用类的内存,成员变量占用内存

但是空类长度会时1

带有虚函数的类,由于需要指针指向类的虚函数表,因此长度为4.

 

15

Code::Code(int a, int b):mem1(a),mem2(b)

只有构造函数有成员初始化列表

c++11之前非静态const成员必须使用此方法初始化,c++11开始支持直接初始化。即类声明中对所有成员进行初始化都可以。

引用数据成员必须使用这种方法初始化。

 

16 关于public派生, is-a关系

基类的私有部分也成为派生类的一部分,但是只能通过基类的公有方法和保护方法才能访问

派生类不能直接访问基类的私有成员

 

17

函数初始化列表,是在对象创建时进行的.此时函数体还未执行.

 

18. 派生类想要初始化基类成员,必须在派生类的初始化成员列表中,利用基类名进行初始化

DerivedClass::DerivedClass(int a, int b):ma(a), BaseClass(b){};

 

19. 基类指针或者引用指向派生类对象,不需要强制类型转换。

一般基类必须包含某个虚函数,派生类重新实现该方法,从而基类指针会动态决定调用基类还是派生类的方法。

这属于动态编联,这也是虚函数存在的意义。

 

20. 为什么析构函数建议是虚函数?

因为19中提到,虚函数能实现动态联编,即基类指针能调用基类或者派生类方法。

因此如果delete baseclasspointer; 那么如果实际指向的是派生类对象,那么将会首先调用派生类的析构函数,然后再调用基类的析构函数。

 

21. 为什么构造函数不能是虚函数?

因为构造函数在初始化基类成员时利用的是成员初始化列表的方法,因此与析构函数的调用方法不同。

 友元函数不能是虚函数,因为它不是类的成员函数。

 

22. 如果派生类中重新定义基类的方法,则将其定义为虚函数,否则定义为非虚函数。

 

23. 基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。

派生类对象将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址。

如果派生类没有重新定义虚函数,该虚函数表将保存函数原始版本的地址

 

24. 第一,如果重新定义了继成的方法,应确保与原来的原型完全相同,但如果返回类型是基类的引用或者指针,则可以修改为指向派生类的引用或者指针,这称为返回类型协变。第二,如果基类声明被重载了,那么派生类需要重新定义所有基类版本,但是可以在新版本中直接调用基类的实现。如果只定义一个版本,其他两个版本将被隐藏。

 

25. 对于保护继承,对于外部世界,保护成员和私有一样,对于派生类,保护成员与公有成员相似。

 

26. 纯虚函数

virtual double Area()  = 0;

一个抽象基类至少包含一个纯虚函数。

纯虚函数不需要有定义。可有可无。

 

27. 假设基类成员使用new创建,派生类如果不使用new,那么派生类的默认拷贝构造函数将使用显式的基类拷贝构造函数来复制派生类对象中基类的部分。没有问题。

如果派生类使用new,那么派生类复制构造函数只能访问派生类的数据,因此必须调用基类的复制构造函数来处理共享的基类数据。

同理,赋值运算符。

总之,当基类和派生类采用new时,派生类的析构函数,复制构造函数,赋值运算符都必须使用相应的方法处理基类的元素。

 

28. 友元函数如何访问基类的成员呢?方法是使用强制类型转换。

std::ostream &operator<<(std::ostream &os, const hasDMA &hs)

{

   os<<(const baseDMA &)hs;

   os<<"test"<<std::endl;

   return os;

 

29 如果语句创建新的对象,则使用初始化,例如拷贝构造函数,如果语句对已有对象进行修改,则是赋值。

Star &Star::operator=(const Star&);

 

30. 不能被继承的函数:构造函数,析构函数,赋值运算符

 

31. 前面提到public继承属于is-a关系,而包含或者私有或者保护继承属于has-a关系。

 

32. valarray类,属于模板类,和vector和array类似。

valarray<int> int_values;

 

33. 对于包含,即类中包含另一对象成员。

构造函数初始化的是成员对象,而不是继承的对象。因此必须在初始化列表中使用的是成员名,而不是类名。

初始化列表中的每一项都调用与之匹配的构造函数。

 

34. 私有继承与包含的相同点:获得实现,但不获得接口。

默认是private继承

 

35. 一般推荐包含。因为继承会导致更多问题,尤其是多继承时。

但是私有继承提供的属性更多,其次当需要重新定义虚函数的时候需要使用继承。

 

36. 如果想要在派生类外面使用基类的方法,有两种方法:

1. 派生类中重新定义一个方法,该方法调用基类的方法。客户调用该方法

2. 使用using指令,在派生类的public中声明基类的方法,从而客户可以调用

using BaseClass::min;

这样客户可以像调用派生类的公有方法一样使用该方法

 

37. A<---B,  A<----C,   B<----D, C<----D

那么D将会包含两个A,从B有一个,从C有一个,产生二义性。

因此需要虚基类,使得D只继承一个A

class B: virtual public A{.......};

class C: virtual public A{.......};

class D: public B, public C{.......};

使用了虚基类,必须使用新的规则:

a  需要显示调用所需基类的构造函数,即显示调用A。因为当基类为虚时,c++禁止信息通过中间类自动传递给基类。

对于非虚基类时,这是非法的。

b 如果B和C都有方法show,则D在调用时必须使用作用域解析运算符来澄清编程者的意图。

d.B::show();

d.C::show();

总之,在祖先想同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。

 

38. 虚基类与抽象基类无关。

虚基类:virtual继承

抽象基类:含有至少一个纯虚函数的类。方法的实现可有可无。

虚基类可以是抽象基类。抽象基类也可以是虚基类。

 

39. 多态:动态多态+静态多态;

静态多态:重载,编译时决定调用哪个函数。

动态多态:重写,运行时决定。三要素:继承,重写,基类指针或引用指向派生类对象

 

40. 如果基类时虚基类,派生类将包含基类的一个子对象,如果基类不是虚基类,派生类将包含多个子对象。

一般情况下,如果某个名称优先与其他所有名称,则使用它时即使不用域作用符,也无二义性。

例如派生类的方法优先与基类方法。

 

41. 类模板

template<class T>

class Stack

{

}

 

template<class T>

Stack<T>::Stack()

{...}

 

42. 别名

typedef const int *(*pa1)[10];

     using pa2 = const in *(*)[10];  c++11支持

 

template<typename T>

using arrtype = std::array<T,12>;

 

43. 无论哪种继承,基类的公有接口都将成为派生类的内部接口,这有时候被称为继承实现,但不继承接口。因为派生类对象不能显示使用基类的接口。

 

44. 友元类

class TV{

public:

 friend class Remote; // remote 可以访问TV的私有部分

}

 

友元函数

class TV{

public:

  friend void Remote::set(TV &t, int c); //set 函数可以访问TV的私有部分。

}

 

互相使用

 class TV;

class Remote{

public: Remote(int m = TV):mode(m)();

}

class TV{

public: friend void Remote::set(TV &t, int c);

}

此外,还有共同友元,即某个函数既是A的友元,又是B的友元函数。

 

45. 嵌套类

对于从A派生而来的类B,A内部的private区域的结构体声明也是不可见的,因为派生类不能直接访问基类的私有部分。

如果结构体声明位于A的public区域,则派生类使用时,需要使用A::来调用

 

46. 异常

1. 调用std::abort()

2. 异常机制

try{}

catch()

{}

 

catch 的参数类型为异常函数的throw内容。

double harm(double a) throw(bad_thing);

c++11支持

double marm() noexcept 表明不抛出异常,优化编译选项等。

 

47. 关于异常的栈解退:

假设函数异常而终止,程序将释放栈中内存,但不是释放到第一个返回地址后停止,而是继续释放栈,知道找到一个位于try块中的返回地址。随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一行语句。

 

48. exception类为基类,自己可以继承并实现以下接口

const char *what(){return "your description"};

此外还提供了stdexcept异常类,包含logic_error, runtime_error类,它们都是从exception公有派生而来。

bad_alloc 异常类,当new失败时,将能catch到该类的引用。

 

49. RTTI 运行阶段类型识别

dynamic_cast 运算符, 只用于包含虚函数的类。

Base *p = dynamic_cast<Base *>(derivedObject);// 向上转换,ok

Derived *p - dynamic_cast<Derived *>(baseObj);//向下转换,返回空指针

 

typeid(Base)==typeid(baseobj)

typeid(baseobj1)==typeid(baseobj2)

 

50.

A a1;

const A *a2 = &a1;

B b1;

B * b2 = &b1;

A *a3 = const_cast<A *>(a2);//ok,删除const,变为可修改

const A *a4 = const_cast<A *>(a2);//ok,增加const,变为不可修改

const B *b3 = const_cast<B *>(a2);//fail,a2不是B*

 

static_cast<>() 同dynamic_cast<>(),区别在于,statc_cast 可以向上和向下转换,而dynamic_cast只能向上,即向基类转化。

 

reinterpret_cast用于天生危险的操作,例如将double赋值给结构体等。

struct dat{..};

long value = 0xA224B1123;

dat *pd = reinterpret_cast<dat *>(&value);

以上是危险操作,但是可以用于底层特殊场合,因此合法。

但是,并不是支持所有类型转换,例如可以将指针转化为足以存贮指针的整型,但是不能转化为更小的浮点或整型。

 

0 0