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