Thinking in C++, Part II

本文围绕C++编程展开,介绍了预处理器、构造函数、析构函数、虚函数等概念。阐述了缺省构造函数与编译器默认构造函数的区别,分析了虚函数不能内联的原因,还提及了重载new和delete、类型转换、智能指针等内容,为C++编程提供了实用指导。

1.预处理器

#include"..."#include<...>的区别

"..."一般是用户提供的头文件,从当前目录开始查找

<...>一般是系统提供的头文件,从环境变量值开始查找

还有常见的预定义名字:

__cplusplus  编译c++

__STDC__  编译标准C

__FILE__  当前编译文件

__LINE__  当前行

__TIME__

__DATA__

2.  class X{

public:

X(int a=3){}; 是不是缺省构造函数?(是)

}

分清什么是缺省构造函数,什么是编译器默认构造函数

3.所谓构造函数真实名符其实吗?是用来构造对象的吗?

  不是,构造函数只是初始化对象数据。  同理,析构函数。。。。。。

4.虚函数能不能内联?  不能,为什么?

5.explicit 关键字有何作用?

explicit只能用作单参数构造函数,指明该构造函数不能用作隐式转换。

class X{

    public:

    int ix;

    explicit X(int i){ix=i;}

};

void f(X){};

void g(int I){

    //! f(I);

}

6.什么时候我们需要用自己的名字空间?希望我们的软件被别人使用时。

7.看这个宏:  #define max(a,b)  ((a)>(b)?(a):(b))

  举一个例子使这个宏崩溃      max(++a,b)

  该怎么做?使用内联   inline int max(int a, int b){return a>b?a:b;}

  为使它能像宏一样接受多类型,模板:  template<class T>

                                      inline const T& max(const T& a, const T& b){return a>b?a:b;}

8.最好不要用typedef来定义数组类型,为什么?

  typedef string d[4];

  string* pal= new d;

  delete pal;

  delete[] pal;

  容易在delete时犯错

9.为什么有必要重载newdelete

  为了改善缺省的newdelete的性能,缺省的new在分配对象时还分配了额外的空间用于管理内存,因此在分配大量而又

小体积的的对象时这种缺省的内存管理存在效率问题,需要重载。

10.一般为了安全将类声明中声明一个私有的拷贝构造函数和一个赋值运算符,但若非要实现拷贝构造函数也一定要实现赋值运算符。

11.构造函数的初始化列表比在函数体内部赋值的好处?

  a.效率,  想想,基类构造函数可能会被调用两次

  b.const的初始化必须在此处

  c.基类无缺省构造函数

12.强制类型转换会引发拷贝构造函数的发生

class X

{public:

X(){};

X(X& a){cout<<"haha..."<<endl;};

};

class Y:public X{};

void main(){

    Y y;

(X)y;

}

13.定位new 可以将对象创建在已经分配好的缓冲池里

class X{

public:

X(){cout<<"constructed"<<endl;}

~X(){cout<<"destructed"<<endl;}

};

void main(){

   char* buf=new char[sizeof(X)*20];

   X* px=new(buf) X;  调用构造函数

   px->~X();  需要显式析构

   delete[] buf;    不需要对定位new的对象调用delete,因为定位new并不分配空间,只是构造对象,但是要手工调用析构函数。

}

14.static可以将名字限制在本文件内可见,但这是c的语法,c++的标准做法是什么?――命名名字空间

15.using std::string使用名字

   using namespace std 使用名字空间

16.重载函数集合是在同一个域中被声明的,注意是声明的,因此它可以是由多个using名字空间引入的

namespace IBM{int print(int);};

namespace DELL{double print(double);};

using namespace IBM;

using namespace DELL;

17.extern "C" 的函数能与c++函数重载吗?  可以

    但是两个extern "C"引入的函数之间不能重载

18.指向具有重载的函数指针,初始化时编译器会为其选择合适的函数

  int ff(int i);

  int ff(double d);

  int (*p)(int)=&ff;

19.再次,常量成员函数的作用是,使一个常量对象可以调用该函数,它不能在内部修改对象成员变量

20.mutable关键字的作用:指明该变量可以在const函数中被修改

21.在模板定义中,关键字typename class同义,如

   template <class T, typename u> ...

22.自定义转换符:

   class B;

   class A{public: A(const B&);};

   class B{public: operator A() const;} //自定义转换符

   void f(const A &)

   B b;

   f(b);//错!  二义性,使用B的自定义转换符呢还是用A的构造函数呢?

23.函数对象:函数对象使一个类,它重载函数调用操作符。

  典型应用,函数对象被作为实参传递给范性算法,这种情况下函数对象较之于函数指针的优点:

  1.可以被内联  2.灵活性

24.什么使内联函数?  谨慎使用内联函数

   a.构造与析构一般不是内联的,代码太多

   b.若函数中使用了静态对象,不要使用内联,有可能形成一个static对象的两个拷贝

25.static修饰成员函数,使之成为静态成员函数,它只能访问静态数据成员

26.    class A{

public:

virtual print(int n=1)

{cout<<"A("<<n<<")"<<endl;}

};

class B: public A{

public:

virtual print(int n=3)

{cout<<"B("<<n<<")"<<endl;}

};

void main(){

A* p=new B;

p->print();

}

输出为: B(1)

解释:虚函数是动态绑定的,但是缺省参数是静态绑定的

27.缺省情况下,单参数构造函数或除第一个参数外其它参数都有缺省初值的构造函数被用作转换函数操作符

编译器可以对对象做隐式转换,为了抑制隐式对象转换需要用explicit作用于构造函数

28.定位new操作符,用于在预分配的空间中构造一个对象

char* area=new char[sizeof(Image)];分配空间

Image* ptr=new(area)Image("A");构造对象

ptr->~Image();析构

ptr=new(area)Image("B");再构造

ptr->~Image();再析构

delete[] area;删除空间

29.要分清拷贝构造与拷贝赋值的差别

void main()

{

Image A("1")构造

Image B=A;拷贝构造

B=A;拷贝赋值

}

30.C++会在幕后做什么?

  class Empty{};

  a.一个拷贝构造函数

  b.一个赋值运算符

  c.一个析构函数

  d.一对取地址运算符(常量,非常量)

  e.若没有声明任何构造函数,它也声明一个缺省构造函数

31.列举非局部静态对象的三种情况:

a.定义在全局名字空间中的对象

b.在类中被声明为static

c.在文件范围内定义为static,(注意是定义不是声明为static,否则是文件局部静态对象)

32. ++,--重载时如何区分前置与后置

    前置无参数,后置有int

   A& operator++();前置

   A& operator++(int);后置

33.类的转换分两种:a.转换函数

                   b.构造函数作转换函数

    typedef char* tName;

    class Token{

public:

    operator tName(){return name;}

    operator int(){return val;}

}

自定义转换函数使一个成员函数,它用作将一个类型转换为另一个类型

单参数构造函数则是将其它类型转换为该类型

34.再论:类型转换

   static_cast相当于C的强制类型转换,只是不能去除const常量性

   const_cast用于常量

   dynamic_cast 用于继承关系中的类型安全转换,前提时有虚函数

   reinterpret_cast 高危险,它依赖于编译器实现,不具有可移植性,其典型应用比如:

改变函数指针类型:

typedef void (*FuncPtr)();

FuncPtr funcPtrArray[10];

int doSomething(){return 1;}

funcPtrArray[0]=reinterpret_cast<FuncPtr>(&doSomething);

35.多态与指针算法和数组混和使用是极其危险的

class B{

public:

   virtual fun()

   {   cout<<"B"<<endl;  }

};

class D: public B{

public:

   int b[100];

   virtual fun()

   {   cout<<"D"<<endl;  }

};

void main(){

    B* p=new D[3];

   p[1].fun();

}

编译器只知道将p按照B的大小进行定位,而这是错误的

36.

static void * operator new(size_t size)

{    cout<<"called"<<endl;

return (void*)0;

}

class A{

public:

//static void * operator new (size_t size)

//{cout<<"static called"<<endl;

 // return (void*)0; }

A() {cout<<"and A() called"<<endl;}

int i;

};

void main(){

A* pa=new A;

}

重载全局new改变了new的基本行为,new的基本行为有二:a.分配内存;b.调用对象构造函数

因此只是执行了打印,即使分配了内存也无法调用合适的构造函数,更谈不上设置虚函数指针了

  如果取消注释部分,那么将调用A的成员new函数,同样有着对象构造上的问题

37.不要重载||,&&,和“,”逗号运算符,为什么?

   因为不能保证短路求值法

38.智能指针  用对象来替代指针这使得局部分配的资源不会因为异常而不得释放

void f(){

A* pa=new A;

pa->m();

delete pa;

}

防止异常需要:

void f(){

A* pa=new A;

try{pa->m();}

catch(...){delete pa;throw;}

delete pa;

}

智能指针版:

void f(){

auto_ptr<A> pa(new A);

pa->m();

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值