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 追加数据如何实现?注意是底层如何实现,而不是如何使用。
关键点是,在追加对象前先判断预留的空间是否满足需求,如果不满足则根据分配策略,另分配足够的空间 (一般使用平方增加策略),复制以前的对象数组,再释放原来的空间,然后把对象追加到尾部。如果任何一个操作环节失败,则至少保留原数组不受影响(异常安全保证策略)。
答:类成员默认访问权限为私有(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 追加数据如何实现?注意是底层如何实现,而不是如何使用。
关键点是,在追加对象前先判断预留的空间是否满足需求,如果不满足则根据分配策略,另分配足够的空间 (一般使用平方增加策略),复制以前的对象数组,再释放原来的空间,然后把对象追加到尾部。如果任何一个操作环节失败,则至少保留原数组不受影响(异常安全保证策略)。