C++句柄、const、Boost配置、复制构造函数、列表初始化
C++ 句柄类
在容器中保存有继承关系的对象时,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初始化)
注: 上面这句话还是存在疑问,关键在于容器是如何保存对象的?对基类和派生类的影
响具体又是什么?
所以唯一可行的选择是容器中保存对象的指针,但是需要用户管理对象和指针。C++中一个通用的技术是包装类(cover)和句柄类(handle),使用句柄类存储和管理类指针。
句柄类大体上完成两方面的工作:
管理指针,这与智能指针的功能类似。
实现多态,利用动态绑定,使得指针既可以指向基类,也可以指向派生类。
具体的实现呢?稍后补上
参考
C++构拷贝(复制)造函数
声明一个类时拷贝构造函数似乎很常见,但总是不太清楚其作用是什么?为什么需要拷贝构造函数?什么情况下需要使用?
参考
拷贝构造函数 是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象,通常用于:
拷贝构造函数接受一个以引用方式传入的当前类的对象作为参数,这个参数是源对象的别名。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果 类带有指针变量,并有动态内存分配, 则它 必须 有一个拷贝构造函数。
常见形式:
1 2 3
| classname (const classname &obj){ }
|
下面看一个例子,对比有复制构造函数和没有复制构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream> using namespace std; class Line { public: int getLength(void); Line(int len); ~Line(); private: int *ptr; };
Line::Line(int len) { cout << "调用构造函数" << endl; ptr = new int; *ptr = len; }
Line::~Line(void) { cout << "释放内存" << endl; delete ptr; }
int Line::getLength(void) { return *ptr; }
void display(Line obj) { cout << "Line 大小: " << obj.getLength() << endl; return; }
int main() { Line line(10); display(line); return 0; }
|
首先会有一个输出结果:
可以看到函数尝试调用两次析构函数以释放内存,但是当调用第二次析构函数时出现下面的情况:
最终导致程序报错,其原因主要由下图所示:
line作为变量传入到函数 display 中时,因为是浅拷贝所以 display 函数中的形参 obj 和 line 指向了相同的内存空间,而函数中的 obj 在执行完成后释放了形参指向的内存空间,但因为 line 和 obj指向相同内存,所以当程序结束尝试释放 line 指向的内存空间时发现并没有内存可以释放,这就导致了程序的错误,所以解决的办法就是使用拷贝构造函数:
1 2 3
| classname (const classname &obj){ }
|
下面看一个例子,对比有复制构造函数和没有复制构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream> using namespace std; class Line { public: int getLength(void); Line(int len); Line(const Line &obj); ~Line(); private: int *ptr; };
Line::Line(int len) { cout << "调用构造函数" << endl; ptr = new int; *ptr = len; }
Line::Line(const Line &obj) { cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl; ptr = new int; *ptr = *obj.ptr; }
Line::~Line(void) { cout << "释放内存" << endl; delete ptr; }
int Line::getLength(void) { return *ptr; }
void display(Line obj) { cout << "Line 大小: " << obj.getLength() << endl; return; }
int main() { Line line(10); display(line); return 0; }
|
程序输出:
1 2 3 4 5
| 调用构造函数 调用拷贝构造函数并为指针 ptr 分配内存 line 大小 : 10 释放内存 释放内存
|
可以看到成功调用了两次析构函数,一次是obj在 display中进行调用的, 另一次是程序结束 line 进行调用的,没有发生错误,安全退出,其结构如下:
下面还有一个例子,看一下什么时候程序会调用拷贝构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class Line { public: int getLength(void); Line(int len); Line(const Line &obj); ~Line();
private: int *ptr; };
Line::Line(int len) { cout << "调用构造函数" << endl; ptr = new int; *ptr = len; }
Line::Line(const Line &obj) { cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl; ptr = new int; *ptr = *obj.ptr; }
Line::~Line(void) { cout << "释放内存" << endl; delete ptr; } int Line::getLength(void) { return *ptr; }
void display(Line obj) { cout << "line 大小 : " << obj.getLength() << endl; }
int main() { Line line1(10); cout << "==========================" << endl; Line line2 = line1; cout << "==========================" << endl; display(line1); cout << "==========================" << endl; display(line2); cout << "==========================" << endl; return 0; }
|
最后输出:
在对象进行复制时因为存在拷贝复制函数,所以发生了深层拷贝,最后在析构时依次析构 line1 和 line2。
c++ 函数前面和后面 使用const 的作用:
参考
前面使用const 表示返回值为const
后面加 const表示函数不可以修改class的成员
对应下面的:
1 2 3
| const int getValue();
int getValue2() const;
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class FunctionConst { public: int value; FunctionConst(); virtual ~FunctionConst(); const int getValue(); int getValue2() const; };
FunctionConst::FunctionConst():value(100) {
}
FunctionConst::~FunctionConst() { }
const int FunctionConst::getValue(){ return value; }
int FunctionConst::getValue2() const{ value = 15; return value; }
|
C++ 赋值初始化和列表初始化
类的构造函数在初始化成员变量时,主要有两种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| CSomeClass::CSomeClass() { x=0; y=1; }
CSomeClass::CSomeClass() : x(0), y(1) { }
|
方式一我们可以称为赋值初始化,通过在函数体内进行赋值初始化,在所有数据成员被 分配内存空间后才进行的。
方式二我们称为列表初始化,在冒号后使用初始化列表进行初始化,给数据成员分配内存空间时就进行初始化
具体
配置boost时出现缺少“*.lib”文件解决(主要是Boost的安装和编译)
参考
主要纪录Boost的正确安装
- 从官网下载原始文件:
这里选择红色框中的文件进行下载
- 之后将文件解压,放到任意目录,这里将文件放在C盘根目录下:
- 进入文件夹,先运行
bootstrap.bat
运行结束后会生成 b2.exe
可执行文件。
- 结束之后开始菜单找到
Visual Studio 2015
文件夹,运行目录下面的 VS2015 X86 本机工具命令提示符
:
- 在命令窗口中定位到刚才解压的
boost
文件夹下,运行如下命令:
b2 -j4 --debug-symbols=on --build-type=complete toolset=msvc-14.0 threading=multi runtime-link=shared address-model=32
需要等待一段时间,即可生成动态链接库
- 在VS2015中进行配置
在项目属性中进行设置,右键项目
—>属性
—>配置属性
a. VC++目录
—>库目录
—> 添加路径:C:\boost_1_71_0\stage\lib
之前就是这里没有添加动态链接库所以发生了如下报错:
LNK1104 无法打开文件 libboost_thread-vc140-mt-gd-x32-1_71.lib
b. C/C++
—> 常规
—> 附加包含目录
—> 添加路径: C:\boost_1_71_0
注意: 一定要在项目中添加了 C++
文件之后,项目属性设置中才会出现 C/C++
的选项。
接下来程序就可以正常运行了。
对比程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <boost/thread.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); }
void threadFun() { for (int i = 0; i < 5; ++i) { wait(1); std::cout << i << std::endl; } }
int main() { boost::thread t(threadFun); t.join(); return 0; }
|