1. C++ 中类与结构体的区别

答:类成员默认访问权限为私有(private),结构体成员默认访问权限为公共(public),其他地方完全一样。

---------------------------------------------------------------------------

2. 关于私有继承

私有继承实际上和组合比较相像,应该说是一个设计概念,而不是具体的实现概念。具体实现中私有继承使用比较少,因为大多数情况使用组合更加清晰。

公有继承意味着 is-a,私有继承意味着 is-implemented-in-terms-of (根据...实现)。


参考:《C++箴言:谨慎使用私有继承》
http://tech.163.com/05/1124/14/23B2S17F0009159Q.html

---------------------------------------------------------------------------

3. 写出下面程序的输出。

#include <stdio.h>

class abc;

void del(abc *pobj)
{
      delete pobj;
}

class abc
{
public:
      abc(){
          printf("abc\r\n");
      }

      ~abc(){
          printf("~abc\r\n");
      }
};


int main(int argc, char *argv[])
{
      abc *pobj = new abc;
      del(pobj);
}

答:
abc

说明:定义 del 函数的时候,abc 的析构函数未定义,因此不会调用。

---------------------------------------------------------------------------

4. 写出下面程序的输出。

#include <stdio.h>
#include <stdlib.h>

void * operator new(size_t size)
{
      printf("malloc %u\r\n", size);
      return malloc(size);
}

void operator delete(void * memblock)
{
      printf("free\r\n");
      return free(memblock);
}

class abc
{
public:
      abc()
      {
          printf("abc\r\n");
          throw int();
      }

      ~abc()
      {
          printf("~abc\r\n");
      }
};

int main(int argc, char * argv[])
{
      try
      {
          new abc;
      }
      catch (int & i)
      {
          printf("%d\r\n", i);
      }

      return 0;
}

答:
malloc 1
abc
free
0
如果将“new abc;”换成“abc a;”,结果将是:
abc
0

说明:
1. 在 C++ 中,构造函数抛出异常后不会触发析构函数的调用,这和 object pascal 不一样。C++ 认为构造失败意味着对象没有产生,既然没有生就没有死。然而,当构造函数抛出异常时,仍会调用 delete 函数以释放内存。

operator new 重载全局 new,所以下面构造的时候 new 肯定会调用该 operator new。operator delete 同样。因此先输出 malloc 1。
new 分配完内存后,会自动调用构造函数,所以输出 abc。
在构造函数内部,抛出异常,throw int();
这个异常被捕获,输出 i,其值为 0。
但是在捕获的异常被处理之前,必须先释放内存。因为异常出错,但此时 new 分配内存的工作已经完成,如果不进行 delete 的话,势必会内存泄露。

2. 当生成堆栈对象时,C++ 自动调用的 operator new 和 operator delete 是全局的 operator new 和 operator delete。

参考:
《More Effective C++》条款10: 在构造函数中防止资源泄漏
…… 不用为 BookEntry 中的非指针数据成员操心,在类的构造函数被调用之前数据成员就被自动地初始化。所以如果 BookEntry 构造函数体开始执行,对象的 theName、theAddress 和 thePhones 数据成员已经被完全构造好了。这些数据可以被看做是完全构造的对象,所以它们将被自动释放,不用你介入操作。……

---------------------------------------------------------------------------

5. 写出下面程序的输出。

#include <stdio.h>

template <typename T>
class abc{
public:
      abc(){
          printf("primary\r\n");
      }
};

template<>
abc<int>::abc()
{
      printf("member spec\r\n");
};

template<typename T, typename P>
class abc<T (*)(P)>
{
public:
      abc(){
          printf("partial spec\r\n");
      }
};

int main(int argc, char *argv[])
{
      abc<void* (*)(int)> f_abc;
      abc<int> i_abc;
}

答:
partial spec
member spec

说明:模板部分特化。

---------------------------------------------------------------------------

6. 下面的代码能否通过编译?为什么?

class a
{
public:
      virtual ~a()
      {
      }
private:
      void operator delete(void *p);
};

int main(int argc, char *argv[])
{
      a _1;
}

答:
不能

说明:
1) 如果一个类有虚析构函数的话,那么自定义 delete 函数必须有函数体。
2) 这个题目中,并不会调用 delete 函数。
3) 对于本题,delete 函数不被调用,但是编译器需要它,因此,不能没有定义。

参考:
如果对象是动态创建(也就是 new 出来的),那么在 delete 的时候系统会先调析构函数然后调 operator delete。编译器在编译的时候会把这 2 个步骤合并到一个函数里,看反汇编就知道了,函数名字类似 'scalar deleting destructor'。如果只是声明了 operator delete 函数而没有定义,那么编译的时候会得不到函数地址,这样生成 'scalar deleting destructor' 内置函数的时候就会报错了。

---------------------------------------------------------------------------

7. C 与 C++ 中 static 函数有什么区别?

      C 语言中 static 关键字作用于函数时起限制函数作用域的作用,其 static 函数作用域被限制为当前文件中,该函数定义之后部分。
      C++ 的全局函数用 static 修饰和 C 语言中一个意思 (C++ 标准建议此种情况用匿名名字空间包含该函数来代替 static 关键字);但类成员函数如果用 static 修饰表示是类的作用域而不是对象作用域,可以直接通过类名来引用。

---------------------------------------------------------------------------

8. const 函数的作用。

      类的设计者通过将函数声明为 const 以表明它们不修改类对象。
      const 函数内不允许修改数据成员 (需要注意的是,虽然在 const 函数内,指针成员变量不允许被修改,但对指针所指向内容的修改是允许的)。
      一个 const 类对象只能调用 const 成员函数 (构造和析构函数除外)。

---------------------------------------------------------------------------

9. 拷贝构造函数什么情况下会用到?实现时有什么注意点?

      拷贝构造函数的几个用处:
      1) 用一个类对象初始化该类的另一个对象的时候:
         A a;
         A b(a);
         A b = a;
      2) 把一个类对象赋值给该类的另一个对象的时候:
         A b;
         b = a;
      3) 传参数和返回时:
         A f (A a) // 传参和返回都会调用拷贝构造函数
         {
             // ...
         }

      实现时要注意的是:
      1) 当类中有指针变量成员时,确认是直接拷贝指针变量的值,还是重新分配内存然后递归拷贝构造。
      2) 是否有每个对象必须有唯一值的成员变量 (比如账号)。其实 1) 也可以归为 2)。

---------------------------------------------------------------------------

10. 写出函数指针,返回指针的函数,const 指针,指向 const 的指针,指向 const 的 const 指针。
      void (* f)()
      void * f()
      int * const f
      const int * f
      const int * const f

---------------------------------------------------------------------------

11. 智能指针。

      智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。
      智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限,引用计数等,控制权转移等)。这个主题可以讲一本书。
      auto_ptr 即是一种常见的智能指针。
      智能指针通常用类模板实现:
          template <class T>
          class smpoint
          {
          public:
              smpoint(T * p): _p(p){}
              T & operator * (){return *_p;}
              T * operator -> (){return _p;}
              ~smpoint(){delete _p;}
          private:
              T * _p;
          }

---------------------------------------------------------------------------

12. 标准模板库 vector 追加数据如何实现?注意是底层如何实现,而不是如何使用。

      关键点是,在追加对象前先判断预留的空间是否满足需求,如果不满足则根据分配策略,另分配足够的空间 (一般使用平方增加策略),复制以前的对象数组,再释放原来的空间,然后把对象追加到尾部。如果任何一个操作环节失败,则至少保留原数组不受影响(异常安全保证策略)。
posted on 2009-09-04 15:48  苏伟  阅读(842)  评论(0编辑  收藏  举报