注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。
测试环境:Ubuntu 10.10
GCC版本:9.2.0
面试问题一:
编写程序判断一个变量是不是指针。
一、指针的判别
1) 拾遗
- C++中仍然支持C语言中的可变参数函数(接收任意类型参数)
- C++编译器的匹配调用优先级
1、重载函数
2、函数模板
3、变参函数
回顾:重载函数、函数模板、变参函数优先级
#include <iostream>
#include <string>
using namespace std;
void test(int i)
{
cout << "void test(int i)" << endl;
}
template
< typename T >
void test(T v)
{
cout << "void test(T v)" << endl;
}
int main(int argc, char *argv[])
{
int i=0;
test(i);
return 0;
}
操作:
1. g++ test.cpp -o test.out编译正确,打印结果:
void test(int i)
分析:
编译器优先匹配了普通函数。编译器找函数时匹配优先级:普通函数>函数模板。
2. 定义变参函数:
#include <iostream>
#include <string>
using namespace std;
void test(int i)
{
cout << "void test(int i)" << endl;
}
template
< typename T >
void test(T v)
{
cout << "void test(T v)" << endl;
}
void test(...)
{
cout << "void test(...)" << endl;
}
int main(int argc, char *argv[])
{
int i=0;
test(i);
return 0;
}
g++ test.cpp -o test.out编译正确,打印结果:
void test(int i)
分析:
编译器优先匹配了普通函数。编译器找函数时匹配优先级:普通函数>变参函数。
3. 只剩下函数模板和变参函数:
#include <iostream>
#include <string>
using namespace std;
/*
void test(int i)
{
cout << "void test(int i)" << endl;
}
*/
template
< typename T >
void test(T v)
{
cout << "void test(T v)" << endl;
}
void test(...)
{
cout << "void test(...)" << endl;
}
int main(int argc, char *argv[])
{
int i=0;
test(i);
return 0;
}
g++ test.cpp -o test.out编译正确,打印结果:
void test(T v)
编译器优先调用了模板函数。编译器找函数时优先级匹配:模板函数>变参函数。
2) 思路
- 将变量分为两类:指针vs非指针
- 编写函数:
*指针变量调用时返回true
*非指针变量调用时返回false
3) 函数模板与变参函数的化学反应
template
< typename T >
bool IsPtr(T* v) //match pointer(匹配任意类型指针)
{
return true;
}
bool IsPtr(...) //match non-pointer(匹配非指针类型)
{
return false;
}
编程实验
指针判断
67-1.cpp
#include <iostream>
#include <string>
using namespace std;
template
<typename T>
bool IsPtr(T* v) //match pointer
{
return true;
}
bool IsPtr(...) //match non-pointer
{
return false;
}
int main(int argc, char *argv[])
{
int i = 0;
int* p = &i;
cout << "p is a pointer: " << IsPtr(p) << endl; //true
cout << "i is a pointer: " << IsPtr(i) << endl; //false
return 0;
}
操作:
1) g++ 67-1.cpp -o 67-1.out编译正确,打印结果:
p is a pointer: 1
i is a pointer: 0
分析:
对于普通类型能区分好指针和非指针类型。
但是对于自定义类类型,可能有问题:
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test()
{
}
virtual ~Test()
{
}
};
template
<typename T>
bool IsPtr(T* v) //match pointer
{
return true;
}
bool IsPtr(...) //match non-pointer
{
return false;
}
int main(int argc, char *argv[])
{
int i = 0;
int* p = &i;
cout << "p is a pointer: " << IsPtr(p) << endl; //true
cout << "i is a pointer: " << IsPtr(i) << endl; //false
Test t;
Test* pt = &t;
cout << "pt is a pointer: " << IsPtr(pt) << endl; //true
cout << "t is NOT a pointer: " << IsPtr(t) << endl; //false
return 0;
}
g++ 67-1.cpp -o 67-1.out编译错误:
67-1.cpp:41:43: error: cannot pass objects of non-trivially-copyable type 'class Test' through '...'
cout << "t is NOT a pointer: " << IsPtr(t) << endl;
错误:'class Test'不能通过对象拷贝给'...'
分析:
如果是类类型,这里传参时需要进行值拷贝。但是无法通过class Test给变参函数(...)完成。
存在的缺陷:
变参函数无法解析对象参数,可能造成程序崩溃!!
进一步的挑战:
如何让编译器精确匹配函数,但不进行实际的调用?
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test()
{
}
virtual ~Test()
{
}
};
template
<typename T>
char IsPtr(T* v) //match pointer
{
return 'd';
}
int IsPtr(...) //match non-pointer
{
return 0;
}
#define ISPTR(p) (sizeof(IsPtr(p)) == sizeof(char))
int main(int argc, char *argv[])
{
int i = 0;
int* p = &i;
cout << "p is a pointer: " << ISPTR(p) << endl; //true
cout << "i is a pointer: " << ISPTR(i) << endl; //false
Test t;
Test* pt = &t;
cout << "pt is a pointer: " << ISPTR(pt) << endl; //true
cout << "t is NOT a pointer: " << ISPTR(t) << endl; //false
return 0;
}
g++ 67-1.cpp -o 67-1.out编译正确,打印结果:
p is a pointer: 1
i is a pointer: 0
p is a pointer: 1
i is a pointer: 0
分析:
编译器编译阶段根据参数类型,判断使用哪个函数,sizeof可以根据函数类型给出相应计算结果(没有调用函数)。
面试问题二:
如果构造函数中抛出异常会发生什么情况?
1、从构造函数功能分析——主要是初始化参数。构造函数异常,构造函数立即停止。
2、导致对象无法完成。
3、析构函数也不会被调用(对象没创建也无法调用析构函数)
4、编译器会将对象所占用的空间立即收回。
解决方法:二阶构造函数。
一、构造中的异常
- 构造过程立即停止
- 当前对象无法生成
- 析构函数不会被调用
- 对象所占用的空间立即收回
二、工程项目中的建议
- 不要在构造函数中抛出异常
- 当构造函数可能产生异常时,使用二阶构造模式
编程实验
构造中的异常(证明总结的那几个现象)
67-2.cpp
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
throw 0; //构造函数抛出异常
}
virtual ~Test()
{
cout << "~Test()" << endl;
}
};
int main(int argc, char *argv[])
{
Test* p = reinterpret_cast<Test*>(1); //定义一个指针指向1
try
{
p = new Test();
}
catch(...)
{
cout << "Exception..." << endl;
}
cout << "p = " << p << endl;
return 0;
}
操作:
1. g++ 67-2.cpp -o 67-2.out编译正确,打印结果:
Test()
Exception...
p = 0x1
分析:
构造函数抛出异常,进行异常处理,处理完毕后,直接打印了'Exception...'。从打印结果可以得出,程序退出后析构函数并没有被调用,说明对象没有创建成功。p打印0x1,p指针没有被赋值,也再次说明对象没有创建成功(new 在抛出异常时,什么都不返回)。
2. 证明内存被回收:
1) 编译文件:g++ -g 67-2.cpp (只是编译器,在编译的时候,产生调试信息)
2) 使用泄露监测工具:valgrind --tool=memcheck --leak-check=full ./a.out
ledk:泄露
监测a.out文件。
in use at exit: 0 bytes in 0 blocks 显示没有内存泄露
no leaks are possible:提示内存没有泄露。证明构造函数发生异常后,会被编译器回收。
3. 注释掉throw 0;
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
//throw 0; //构造函数抛出异常
}
virtual ~Test()
{
cout << "~Test()" << endl;
}
};
int main(int argc, char *argv[])
{
Test* p = reinterpret_cast<Test*>(1); //定义一个指针指向1
try
{
p = new Test();
}
catch(...)
{
cout << "Exception..." << endl;
}
cout << "p = " << p << endl;
return 0;
}
重复操作1和2
在程序退出后还有4个字节被使用。
三、析构中的异常
避免在析构函数中抛出异常!!
析构函数的异常将导致:
对象所使用的资源无法完全释放。
小结
1)C++中依然支持变参函数
2)变参函数无法很好的处理对象参数
3)利用函数模板和变参函数能够判断指针变量
4)构造函数和析构函数中不要抛出异常