20059
备注:
|
24007
|
删除的内容标记成这样。 | 加入的内容标记成这样。 |
行号 77: | 行号 77: |
hour = h; minute = m; second = s; | hour = h; minute = m; second = s; |
行号 85: | 行号 87: |
Clock c1; | Clock c1, c2; |
行号 87: | 行号 89: |
c2.SetTime(18, 00, 00); | |
行号 88: | 行号 91: |
c2.ShowTime(); | |
行号 111: | 行号 115: |
Clock c1; | Clock c1, c2; |
行号 113: | 行号 117: |
c2.SetTime(18, 00, 00); | |
行号 114: | 行号 119: |
c2.ShowTime(); | |
行号 128: | 行号 134: |
Student stud1; | Student stud1, stud2; |
行号 131: | 行号 137: |
stud2.display(); | |
行号 140: | 行号 147: |
next.hour = 0; // 同C语言结构体类似,可以访问成员 | next.hour = 10; // 同C语言结构体类似,可以访问成员 next.minute = 10; next.second = 30; |
行号 143: | 行号 152: |
next.SetTime(9, 40, 1); | |
行号 152: | 行号 160: |
my_clock->hour = 10; //通过指针访问成员 | Clock * now = new Clock; now->hour = 10; //通过指针访问成员 now->minute = 10; now->second = 30; |
行号 156: | 行号 167: |
delete now; | |
行号 186: | 行号 198: |
struct Clock { int second, minute, hour; inline void ShowTime(); }; |
|
行号 212: | 行号 228: |
成员函数也可以有缺省参数 | 成员函数也可以有缺省参数。缺省值写在声明中,而不是定义中。 |
行号 219: | 行号 235: |
hour = h; minute = m; second = s; | hour = h; minute = m; second = s; |
行号 227: | 行号 245: |
int hour, minute, second; | int hour, minute, second; |
行号 229: | 行号 247: |
void SetTime(int yy, int mm, int dd); void ShowTime(); |
void SetTime(int yy, int mm, int dd); void ShowTime(); |
行号 237: | 行号 255: |
d.SetTime(8, 27, 0); // 访问public, ok d.hour = 10; // 访问private错误 |
d.SetTime(8, 27, 0); // 类内访问public, ok d.hour = 10; // 类内访问private错误 |
行号 241: | 行号 259: |
hour = h; //访问private, ok | hour = h; //类内访问private, ok |
行号 258: | 行号 276: |
另一个例子: {{{#!cplusplus class Student{ private: char num[20]; char name[10]; bool gender; public: void display() { cout << num << name << (gender?"male":"female"); } void setnum(char n[]) { strcpy(num, n); } void setname(char n[]) { strcpy(name, n); } void setgender(bool g) { gender = g; } }; int main() { Student stud1; stud1.setname("jack"); stud1.setnum("05020001"); stud1.setgender(true); stud1.display(); cout << stud1.gender; //error } }}} '''并非'''所有数据成员都是private,所有成员函数都是公有的。 {{{#!cplusplus class Clock { private: //只能在类内访问 int hour, minute, second; public: //可以在类外访问 void Add(int s) { for(int i = 0; i < s; i++) inc(); } private: void inc() { second++; if(second == 60) { second = 0; minute ++; if(minute == 60) { minute = 0; hour++; if(hour == 24) hour = 0; } } } }; }}} |
|
行号 262: | 行号 339: |
int i = 5; // 定义普通变量,定义的同时初始化 cout << i; // 访问变量 Clock time; // 定义对象,定义了以后没有初始化 time.ShowTime(); //错误,没有初始化 time.SetTime(2000, 5, 1); //需要调用初始化函数后 time.ShowTime(); //才能够访问 |
int main() { int i = 5; // 定义普通变量,定义的同时初始化 cout << i; // 访问变量 Clock time; // 定义对象,定义的同时并没有初始化 time.ShowTime(); //错误,没有初始化 time.SetTime(2000, 5, 1); //需要调用初始化函数后 time.ShowTime(); //才能够访问 } |
行号 273: | 行号 352: |
int hour, minute, second; public: /*构造函数,函数名与类名相同,没有返回类型*/ Clock(){ hour = minute = second = 0; } void ShowTime() { cout << hour << minute << second; } }; int main() { Clock c1; // 定义对象,定义的同时并且初始化 c1.ShowTime(); } }}} 这种没有参数的构造函数称作默认构造函数(default constructor)。 如果要在定义对象的同时将对象初始化成某一个给定的值,则可以给构造函数添加参数: {{{#!cplusplus class Clock{ int hour, minute, second; public: Clock(int h, int m, int s) { hour = h; minute = m; second = s; } void ShowTime() { cout << hour << minute << second; } }; int main() { Clock c1(10, 10, 30); c1.ShowTime(); Clock c2; //错误,缺少参数不能完成初始化 c2.ShowTime(); } }}} 构造函数也可以定义在类外: {{{#!cplusplus class Clock{ int hour, minute, second; |
|
行号 276: | 行号 399: |
}}} 构造函数的名字与类名相同,没有返回类型。它利用特定的值构造对象,把对象初始化为一个特定的状态。 {{{#!cplusplus |
|
行号 281: | 行号 400: |
hour = h; minute = m; second = s; } }}} 带构造函数的对象定义 {{{#!cplusplus |
hour = h; minute = m; second = s; } |
行号 288: | 行号 407: |
Clock sleep(22, 30); | |
行号 289: | 行号 409: |
Clock *pC = new Clock(23, 30, 25); | |
行号 290: | 行号 411: |
Clock *pC = new Clock(23, 30, 25); } |
} }}} 构造函数初始化列表 {{{#!cplusplus class Clock{ int hour, minute, second; public: Clock(int h, int m, int s) : hour(h), minute(m), second(s) { } void ShowTime() { cout << hour << minute << second; } }; }}} 初始化列表用在类中内嵌对象的情况: {{{#!cplusplus class WorldClock { Clock clock; int timezone; public: WorldClock(int hour, int minute, int second, int t) :clock(hour, minute, second), timezone(t) { } }; |
行号 301: | 行号 448: |
Clock(const char *); | Clock(const char *s); |
行号 310: | 行号 457: |
Clock early(6); Clock *pC = new Clock("23:30:0"); |
|
行号 311: | 行号 460: |
Clock *pC = new Clock("23:30:0"); } }}} 一个类要能够默认构造对象,需要:这个类一个构造函数都没有写(系统会自动生成一个默认构造函数),或者至少写有一个默认构造函数。一个所有参数具有默认参数的构造函数也是默认构造函数,比如 |
} }}} 一个类中一个构造函数都没有写,编译器会自动生成一个默认构造函数。 |
行号 319: | 行号 467: |
}; int main() { Clock c1; //没有初始化 } }}} 自动生成的构造函数可以调用内嵌类型的默认构造函数(如果有默认构造函数的话)。 {{{#!cplusplus class Student { string name; string num; bool gender; public: void Show() { cout << name << num << gender; } }; int main() { Student stud; stud.Show(); } }}} 一个所有参数具有默认参数的构造函数也是默认构造函数,比如 {{{#!cplusplus class Clock { int hour, minute, second; |
|
行号 322: | 行号 498: |
int main() { Clock c1; } |
|
行号 327: | 行号 506: |
int hour, minute, second; public: ~Clock(); }; }}} 析构函数的名字为类名前加~,没有返回类型和参数。析构函数不能重载。例如: |
int hour, minute, second; public: ~Clock() { //作一些资源释放操作 } }; }}} 析构函数的名字为类名前加~,没有返回类型和参数。析构函数不能重载。析构函数会被自动调用,例如: |
行号 346: | 行号 527: |
析构函数一般用在需要资源释放的地方,比如: {{{#!cplusplus class String{ char *s; public: String(char *str) { s = new char[strlen(str)+1]; strcpy(s, str); } ~String() { delete[] s; } }; int main() { String s("Hello"); } }}} |
|
行号 352: | 行号 552: |
Clock *d4 = (Clock *)malloc( sizeof(Clock) ); | Clock *d4 = (Clock *)malloc( sizeof(Clock) ); // 没有初始化 |
行号 355: | 行号 555: |
delete[ ] d3; | delete[] d3; |
行号 367: | 行号 567: |
C++类与对象
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);
8
9 int main() {
10 complex x, y;
11 x.real = 10.0;
12 x.imag = 20.0;
13 y.real = 1.0;
14 y.imag = -2.0;
15 complex z = add(x, y);
16 }
另一个例子:
再一个例子:
2. C++的类
C++的做法是在C语言的基础上更进一步,将数据与操作这组数据的函数结合在一起,构成类(class)
C++中类的定义可以用struct或者用class。函数与数据结合在一起,逻辑关系更加明确。类中的函数又被称为方法、成员函数。C++类中函数的定义,函数体可以直接写在类的内部,写在头文件中:
1 struct Clock {
2 int hour, minute, second;
3 void SetTime(int h, int m, int s) {
4 hour = h;
5 minute = m;
6 second = s;
7 }
8 void ShowTime() {
9 cout << hour << minute << second;
10 }
11 };
12
13 int main() {
14 Clock c1, c2;
15 c1.SetTime(10, 10, 30);
16 c2.SetTime(18, 00, 00);
17 c1.ShowTime();
18 c2.ShowTime();
19 }
也可以分开定义,比如:
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 }
17
18 // main.cpp源文件
19 int main() {
20 Clock c1, c2;
21 c1.SetTime(10, 10, 30);
22 c2.SetTime(18, 00, 00);
23 c1.ShowTime();
24 c2.ShowTime();
25 }
另一个例子:
对象的定义和访问方式类似于结构体
在堆空间分配和访问对象
定义对象数组
成员函数和普通函数一样可以内联。函数体写在类定义内部的成员函数,默认就是内联的。
类外定义函数体的成员函数,要定义成内联需要加inline关键字,并把函数体写在头文件中
成员函数也可以重载
成员函数也可以有缺省参数。缺省值写在声明中,而不是定义中。
3. 类的访问权限
公有部分定义了类的外部接口,可供类的使用者调用。私有部分隐藏了类的具体实现,由类的实现者实现,不需要使用者关心。这就是封装。private和public为新增的关键字。
struct与class的访问权限的区别:struct默认public,class默认为private
另一个例子:
1 class Student{
2 private:
3 char num[20];
4 char name[10];
5 bool gender;
6 public:
7 void display() {
8 cout << num << name << (gender?"male":"female");
9 }
10 void setnum(char n[]) {
11 strcpy(num, n);
12 }
13 void setname(char n[]) {
14 strcpy(name, n);
15 }
16 void setgender(bool g) {
17 gender = g;
18 }
19 };
20
21 int main() {
22 Student stud1;
23 stud1.setname("jack");
24 stud1.setnum("05020001");
25 stud1.setgender(true);
26 stud1.display();
27 cout << stud1.gender; //error
28 }
并非所有数据成员都是private,所有成员函数都是公有的。
1 class Clock {
2 private: //只能在类内访问
3 int hour, minute, second;
4 public: //可以在类外访问
5 void Add(int s) {
6 for(int i = 0; i < s; i++)
7 inc();
8 }
9 private:
10 void inc() {
11 second++;
12 if(second == 60) {
13 second = 0;
14 minute ++;
15 if(minute == 60) {
16 minute = 0;
17 hour++;
18 if(hour == 24)
19 hour = 0;
20 }
21 }
22 }
23 };
4. 构造与析构
定义变量的同时完成初始化(resource acquisition is initialization)是一种好的编程习惯,可以避免错误。
为避免偶然使用没有初始化的对象的错误,可以在类中定义一个特殊的函数——构造函数。它在对象被定义时,就被自动调用,以确保完成初始化。
这种没有参数的构造函数称作默认构造函数(default constructor)。
如果要在定义对象的同时将对象初始化成某一个给定的值,则可以给构造函数添加参数:
1 class Clock{
2 int hour, minute, second;
3 public:
4 Clock(int h, int m, int s) {
5 hour = h;
6 minute = m;
7 second = s;
8 }
9 void ShowTime() {
10 cout << hour << minute << second;
11 }
12 };
13
14 int main() {
15 Clock c1(10, 10, 30);
16 c1.ShowTime();
17 Clock c2; //错误,缺少参数不能完成初始化
18 c2.ShowTime();
19 }
构造函数也可以定义在类外:
1 class Clock{
2 int hour, minute, second;
3 public:
4 Clock(int h, int m, int s);
5 };
6 Clock::Clock( int h, int m, int s) {
7 hour = h;
8 minute = m;
9 second = s;
10 }
11
12 int main() {
13 Clock now; //错误
14 Clock sleep(22, 30);
15 Clock getup(6, 30, 30);
16 Clock *pC = new Clock(23, 30, 25);
17 Clock now = Clock(9, 21, 20);
18 }
构造函数初始化列表
初始化列表用在类中内嵌对象的情况:
构造函数可以重载,也可以有缺省参数:
各个构造函数所需要参数不同,但构造函数都没有返回类型。在定义对象时,会根据传递的参数来选择一个特定的构造函数初始化对象。
一个类中一个构造函数都没有写,编译器会自动生成一个默认构造函数。
自动生成的构造函数可以调用内嵌类型的默认构造函数(如果有默认构造函数的话)。
一个所有参数具有默认参数的构造函数也是默认构造函数,比如
对象被摧毁时,一个成员函数也会自动被调用,这个函数称为称为析构函数,一般完成资源的释放工作
析构函数的名字为类名前加~,没有返回类型和参数。析构函数不能重载。析构函数会被自动调用,例如:
析构函数一般用在需要资源释放的地方,比如:
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 };
6. 类的组合
构造复杂的对象的一种方法是组合。一个类可以使用另一个类的对象作为成员,比如:
1 class point{
2 private:
3 double x, y;
4 public:
5 //...
6 };
7
8 class line {
9 private:
10 point start, end;
11 public:
12 //...
13 };
14
15 class circle {
16 private:
17 point center;
18 double radius;
19 public:
20 //...
21 };
22
23 class rectangle {
24 private:
25 point p1, p2;
26 public:
27 //...
28 };
组合对象的构造
1 class point{
2 public:
3 double x, y;
4 point(double x0 = 0, double y0 = 0) {
5 x = x0;
6 y = y0;
7 cout << "point " << x << y << "initialized!" << endl;
8 }
9 };
10
11 class line {
12 public:
13 point start, end;
14 line(double x0, double y0, double x1, double y1) {
15 cout << "line initializing" << endl;
16 start.x = x0;
17 start.y = y0;
18 end.x = x1;
19 end.y = y1;
20 }
21 };
从这个程序的运行结果可以看出,在line类的构造函数执行前,start和end对象已经构造完成。point类没有默认构造函数,line不能构造。如果x、y是私有的,那么在line构造函数中不能对他们进行赋值。解决办法:使用构造函数初始化列表
1 class point{
2 private:
3 double x, y;
4 public:
5 point(double x0, double y0) {
6 x = x0;
7 y = y0;
8 cout << "point " << x << y << "initialized!" << endl;
9 }
10 };
11
12 class line {
13 private:
14 point start, end;
15 public:
16 line(double x0, double y0, double x1, double y1)
17 : start(x0, y0), end(x1, y1) {
18 cout << "line initializing" << endl;
19 }
20 };
在line构造函数的初始化列表中,给start、end成员的构造函数传递参数完成它们的初始化。
初始化表还能够解决其它一些类型的成员的初始化问题,比如const成员,引用成员等。比如
构造函数和析构函数执行的顺序
1 class point{
2 private:
3 double x, y;
4 public:
5 point(double a, double b) :x(a), y(b) {
6 cout << "construct point" << endl;
7 }
8 ~point() {
9 cout << "destruct point" << endl;
10 }
11 };
12 class line {
13 private:
14 point start, end;
15 public:
16 line(double x0, double y0, double x1, double y1)
17 : start(x0, y0) , end(x1, y1) {
18 cout << "construct line" << endl;
19 }
20 ~line() {
21 cout << "destruct line" << endl;
22 }
23 };
7. 静态成员
C++的类有一种特殊的成员,它的空间是这个类的所有对象共享的,称为类静态数据成员
静态成员变量的定义、初始化和访问
1 class Task {
2 public:
3 Task() : x(n) { n++; }
4 ~Task() { n--; }
5 int x;
6 static unsigned n;
7 };
8 unsigned Task::n = 0;
9
10 int main() {
11 Task s1;
12 cout << s1.x << s1.n << endl;
13 Task s2;
14 cout << s1.x << s1.n << endl;
15 cout << s2.x << s2.n << endl;
16 cout << Task::n << endl;
17 }
静态成员函数:只能访问静态成员的成员函数
静态成员用法举例:写一个类Singleton,这个类只能存在一个对象。
8. 友元
友元提供了让其它函数或类直接访问私有成员的途径。友元函数是能够直接访问类的私有成员的函数。
class Point { private: int x, y; public: int GetX() { return x; } friend bool isequal(Point a, Point b); }; bool isequal(Point a, Point b) { return a.x == b.x && a.y == b.y; }
友元类
友元关系是单向的,并不可传递。
== const对象 ==
定义对象前面加const,表示对象不能改变
1 class Clock{
2 int hour, minute, second;
3 public:
4 Clock(int h, int m, int s)
5 : hour(h), minute(m), second(s)
6 {
7 }
8 void SetTime(int h, int m, int s) {
9 hour = h;
10 minute = m;
11 second = s;
12 }
13 void ShowTime() {
14 cout << hour << minute << second;
15 }
16 };
17 int main() {
18 const int i = 10;
19 i++; //error!
20 const Clock t(10, 30, 20);
21 cout << t.second;
22 t.second = 10; // error
23 t.SetTime(10, 20, 10); // error!
24 t.ShowTime(); // error!
25 }
成员函数声明后面加const,表示这个成员函数不修改数据成员。这样的函数可以被const对象调用。
常成员函数可以被常对象调用,也可以被普通对象调用。
常成员函数与非常成员函数可以重载
1 class Array {
2 private:
3 int *p, size;
4 public:
5 Array(int s) {
6 size = s;
7 p = new int [ size ];
8 }
9 ~Array() { delete[ ] p; }
10 int & at( int i) {
11 if( i < size) return p[i]; else throw out_of_range();
12 }
13 int at( int i) const {
14 if( i < size) return p[i]; else throw out_of_range();
15 }
16 };
17 int main() {
18 Array a(10);
19 a.at(5) = 5;
20 const Array b(5);
21 b.at(5) = 5; // error
22 }
9. this指针
1 void f() {
2 Clock time(1999, 9, 9);
3 time.set_second(20);
4 time.set_minute(30)
5 time.set_hour(10);
6 time.set_second(20).set_minute(30).set_hour(10);
7 }
8 class Clock {
9 public:
10 Clock &set_second(int n);
11 Clock &set_minute(int n);
12 Clock &set_hour(int n);
13 };
14 Clock &Clock::set_second(int n) {
15 second = n;
16 return *this;
17 }
this有两种类型:以Clock类为例,Clock* const和const Clock* const