⇤ ← 于2006-06-25 20:37:36修订的的版本1
8378
备注:
|
11300
|
删除的内容标记成这样。 | 加入的内容标记成这样。 |
行号 1: | 行号 1: |
行号 3: | 行号 2: |
[[TableOfContents]] |
|
行号 193: | 行号 194: |
== 构造函数constructor == | == 构造与析构 == |
行号 302: | 行号 303: |
== 对象的复制 == 对象在两种情况下发生复制:赋值和拷贝构造。拷贝构造是指创建一个对象,创建的同时让它的值与另一个已存在的对象一模一样。赋值是复制另一个同类型的值给已存在的对象,使它变成新的值。比如 {{{#!cplusplus void f( Date d ); int main() { Date today; //默认构造函数 Date d = today; //拷贝构造 Date s( today ); //拷贝构造 d = another_day; //赋值 f(s); //拷贝构造 } }}} 拷贝构造的工作由拷贝构造函数完成。如果自己没有编写拷贝构造函数,编译器会自动生成一个拷贝构造函数,用于完成缺省的拷贝构造的功能:用源对象的所有数据成员逐一拷贝构造目标对象的相应成员。我们可以自定义拷贝构造函数去改写默认的拷贝构造函数。 {{{#!cplusplus class Clock { int hour, minute, second; public: Clock(Clock& c) { hour = c.hour; minite = c.minute; second = 0; } }; }}} 何时需要自定义拷贝构造函数、析构函数?当涉及到内存管理时,一个类中有指针成员,指向动态分配的空间,那么通常需要写一组拷贝构造函数、析构函数和赋值操作符。写一个类Array,模拟数组的功能,元素是int类型,数组的大小可以是变量,增加检查越界的功能。即可以这样使用: {{{#!cplusplus int main() { Array arr(10); // 相当于int arr[10]; arr.at(0) = 3; // 相当于arr[0] = 3; arr.at(1) = 5; // 相当于arr[1] = 5; } }}} {{{#!cplusplus class Array { private: int *p; int size; public: Array(int s) { size = s; p = new int [ size ]; } ~Array() { delete[ ] p; } int & at( int i) { if( i < size) return p[i]; else return 0; } }; }}} 但是这个类在这样使用时存在问题: {{{#!cplusplus int main() { Array arr( 10 ); arr.at(0) = 10; Array arr2(arr); // error here } }}} 拷贝构造的对象与原对象共享同一块空间,修改了一个对象,另一个对象也受影响。当一个对象释放时,另一个对象也不能使用了。当两个对象都被释放时,同一个空间释放了两次。解决办法是自定义拷贝构造函数: {{{#!cplusplus class Array { private: int *p; int size; public: Array(int s) { size = s; p = new int [ size ]; } ~Array() { delete[ ] p; } int & at( int i) { if( i < size) return p[i]; else return 0; } Array( Array &a) { size = a.size; p = new int [size]; for(int i = 0; i < size; i++) p[i] = a.p[i]; } }; }}} |
C++类与对象
1. 问题
- C++语言只提供了整数、浮点数、bool、字符等基本类型。如何处理系统没有内置的类型?比如复数?时间?日期?坐标?
传统C语言的做法:
2. C++的类
- C++的做法:将函数与数据结合在一起,构成类class
C++中类的定义可以用struct或者用class。函数与数据结合在一起,逻辑关系更加明确。类中的函数又被称为方法、成员函数。C++类中函数的定义,函数体可以直接写在类的内部,写在头文件中:
也可以分开定义,比如:
1 //clock.h头文件:
2 struct Clock {
3 int hour, minute, second;
4 void SetTime(int h, int m, int s);
5 void ShowTime();
6 };
7
8 //clock.cpp源文件:
9 void Clock::SetTime(int h, int m, int s) {
10 hour = h;
11 minute = m;
12 second = s;
13 }
14 void Clock::ShowTime() {
15 cout << hour << minute << second;
16 }
对象的定义和访问方式类似于结构体
在堆空间分配和访问对象
定义对象数组
成员函数和普通函数一样可以内联、重载、带默认值。函数体写在类定义内部的成员函数,默认就是内联的。
类外定义函数体的成员函数,要定义成内联需要加inline关键字,并把函数体写在头文件中
3. 类的访问权限
公有部分定义了类的外部接口,可供类的使用者调用。私有部分隐藏了类的具体实现,由类的实现者实现,不需要使用者关心。这就是封装。private和public为新增的关键字。
struct与class的访问权限的区别:struct默认public,class默认为private
4. 构造与析构
定义变量的同时完成初始化(resource acquisition is initialization)是一种好的编程习惯,可以避免错误。
为避免偶然使用没有初始化的对象的错误,可以在类中定义一个特殊的函数——构造函数。它在对象被定义时,就被自动调用,以确保完成初始化。
构造函数的名字与类名相同,没有返回类型。它利用特定的值构造对象,把对象初始化为一个特定的状态。
带构造函数的对象定义
构造函数可以重载,也可以有缺省参数:
各个构造函数所需要参数不同,但构造函数都没有返回类型。在定义对象时,会根据传递的参数来选择一个特定的构造函数初始化对象。
一个类要能够默认构造对象,需要:这个类一个构造函数都没有写(系统会自动生成一个默认构造函数),或者至少写有一个默认构造函数。一个所有参数具有默认参数的构造函数也是默认构造函数,比如
对象被摧毁时,一个成员函数也会自动被调用,这个函数称为称为析构函数,一般完成资源的释放工作
析构函数的名字为类名前加~,没有返回类型和参数。析构函数不能重载。例如:
new会自动调用构造函数,而malloc不能。delete会自动调用析构函数,而free不能。
要定义对象数组,并且没有初始化,那么要求该类可以默认构造对象。比如:
5. 对象的复制
对象在两种情况下发生复制:赋值和拷贝构造。拷贝构造是指创建一个对象,创建的同时让它的值与另一个已存在的对象一模一样。赋值是复制另一个同类型的值给已存在的对象,使它变成新的值。比如
拷贝构造的工作由拷贝构造函数完成。如果自己没有编写拷贝构造函数,编译器会自动生成一个拷贝构造函数,用于完成缺省的拷贝构造的功能:用源对象的所有数据成员逐一拷贝构造目标对象的相应成员。我们可以自定义拷贝构造函数去改写默认的拷贝构造函数。
何时需要自定义拷贝构造函数、析构函数?当涉及到内存管理时,一个类中有指针成员,指向动态分配的空间,那么通常需要写一组拷贝构造函数、析构函数和赋值操作符。写一个类Array,模拟数组的功能,元素是int类型,数组的大小可以是变量,增加检查越界的功能。即可以这样使用:
但是这个类在这样使用时存在问题:
拷贝构造的对象与原对象共享同一块空间,修改了一个对象,另一个对象也受影响。当一个对象释放时,另一个对象也不能使用了。当两个对象都被释放时,同一个空间释放了两次。解决办法是自定义拷贝构造函数:
1 class Array {
2 private:
3 int *p;
4 int size;
5 public:
6 Array(int s) {
7 size = s;
8 p = new int [ size ];
9 }
10 ~Array() {
11 delete[ ] p;
12 }
13 int & at( int i) {
14 if( i < size)
15 return p[i];
16 else
17 return 0;
18 }
19 Array( Array &a) {
20 size = a.size;
21 p = new int [size];
22 for(int i = 0; i < size; i++)
23 p[i] = a.p[i];
24 }
25 };