11300
备注:
|
26553
|
删除的内容标记成这样。 | 加入的内容标记成这样。 |
行号 6: | 行号 6: |
* C++语言只提供了整数、浮点数、bool、字符等基本类型。如何处理系统没有内置的类型?比如复数?时间?日期?坐标? 传统C语言的做法: |
C++语言只提供了整数、浮点数、bool、字符等基本类型。如何处理系统没有内置的类型?比如复数?时间?日期?坐标?传统C语言的做法是使用结构体,比如: |
行号 17: | 行号 15: |
}}} |
int main() { complex x, y; x.real = 10.0; x.imag = 20.0; y.real = 1.0; y.imag = -2.0; complex z = add(x, y); } }}} 另一个例子: |
行号 33: | 行号 41: |
}}} 再一个例子: {{{#!cplusplus struct Student{ char num[20]; char name[10]; bool gender; }; void display(Student &s) { cout << s.num << s.name << (s.gender?"male":"female"); } int main() { Student stud1, stud2; // initialize display(stud1); display(stud2); } |
|
行号 36: | 行号 64: |
* C++的做法:将函数与数据结合在一起,构成类class | C++的做法是在C语言的基础上更进一步,将数据与操作这组数据的函数结合在一起,构成类(class) |
行号 49: | 行号 77: |
hour = h; minute = m; second = s; | hour = h; minute = m; second = s; |
行号 55: | 行号 85: |
int main() { Clock c1, c2; c1.SetTime(10, 10, 30); c2.SetTime(18, 00, 00); c1.ShowTime(); c2.ShowTime(); } |
|
行号 74: | 行号 112: |
}}} | // main.cpp源文件 int main() { Clock c1, c2; c1.SetTime(10, 10, 30); c2.SetTime(18, 00, 00); c1.ShowTime(); c2.ShowTime(); } }}} 另一个例子: {{{#!cplusplus struct Student{ char num[20]; char name[10]; bool gender; void diplay() { cout << num << name << (gender?"male":"female"); } }; int main() { Student stud1, stud2; // initialize stud1.display(); stud2.display(); } }}} |
行号 81: | 行号 147: |
next.hour = 0; // 同C语言结构体类似,可以访问成员 | next.hour = 10; // 同C语言结构体类似,可以访问成员 next.minute = 10; next.second = 30; |
行号 84: | 行号 152: |
next.SetTime(9, 40, 1); | |
行号 93: | 行号 160: |
my_clock->hour = 10; //通过指针访问成员 | Clock * now = new Clock; now->hour = 10; //通过指针访问成员 now->minute = 10; now->second = 30; |
行号 97: | 行号 167: |
delete now; | |
行号 115: | 行号 186: |
成员函数和普通函数一样可以内联、重载、带默认值。函数体写在类定义内部的成员函数,默认就是内联的。 | 成员函数和普通函数一样可以内联。函数体写在类定义内部的成员函数,默认就是内联的。 |
行号 124: | 行号 195: |
行号 126: | 行号 198: |
struct Clock { int second, minute, hour; inline void ShowTime(); }; |
|
行号 131: | 行号 207: |
成员函数也可以重载 | |
行号 136: | 行号 212: |
hour = h; minute = m; second = 0; | hour = h; minute = m; second = 0; |
行号 139: | 行号 217: |
hour = h; minute = second = 0; | hour = h; minute = second = 0; |
行号 149: | 行号 228: |
成员函数也可以有缺省参数。缺省值写在声明中,而不是定义中。 | |
行号 155: | 行号 235: |
hour = h; minute = m; second = s; | hour = h; minute = m; second = s; |
行号 163: | 行号 245: |
int hour, minute, second; | int hour, minute, second; |
行号 165: | 行号 247: |
void SetTime(int yy, int mm, int dd); void ShowTime(); |
void SetTime(int yy, int mm, int dd); void ShowTime(); |
行号 173: | 行号 255: |
d.SetTime(8, 27, 0); // 访问public, ok d.hour = 10; // 访问private错误 |
d.SetTime(8, 27, 0); // 类内访问public, ok d.hour = 10; // 类内访问private错误 |
行号 177: | 行号 259: |
hour = h; //访问private, ok | hour = h; //类内访问private, ok |
行号 194: | 行号 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; } } } }; }}} |
|
行号 198: | 行号 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(); //才能够访问 } |
行号 209: | 行号 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; |
|
行号 212: | 行号 399: |
}}} 构造函数的名字与类名相同,没有返回类型。它利用特定的值构造对象,把对象初始化为一个特定的状态。 {{{#!cplusplus |
|
行号 217: | 行号 400: |
hour = h; minute = m; second = s; } }}} 带构造函数的对象定义 {{{#!cplusplus |
hour = h; minute = m; second = s; } |
行号 224: | 行号 407: |
Clock sleep(22, 30); | |
行号 225: | 行号 409: |
Clock *pC = new Clock(23, 30, 25); | |
行号 226: | 行号 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) { } }; |
行号 237: | 行号 448: |
Clock(const char *); | Clock(const char *s); |
行号 246: | 行号 457: |
Clock early(6); Clock *pC = new Clock("23:30:0"); |
|
行号 247: | 行号 460: |
Clock *pC = new Clock("23:30:0"); } }}} 一个类要能够默认构造对象,需要:这个类一个构造函数都没有写(系统会自动生成一个默认构造函数),或者至少写有一个默认构造函数。一个所有参数具有默认参数的构造函数也是默认构造函数,比如 |
} }}} 一个类中一个构造函数都没有写,编译器会自动生成一个默认构造函数。 |
行号 255: | 行号 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; |
|
行号 258: | 行号 498: |
int main() { Clock c1; } |
|
行号 263: | 行号 506: |
int hour, minute, second; public: ~Clock(); }; }}} 析构函数的名字为类名前加~,没有返回类型和参数。析构函数不能重载。例如: |
int hour, minute, second; public: ~Clock() { //作一些资源释放操作 } }; }}} 析构函数的名字为类名前加~,没有返回类型和参数。析构函数不能重载。析构函数会被自动调用,例如: |
行号 282: | 行号 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"); } }}} |
|
行号 288: | 行号 552: |
Clock *d4 = (Clock *)malloc( sizeof(Clock) ); | Clock *d4 = (Clock *)malloc( sizeof(Clock) ); // 没有初始化 |
行号 291: | 行号 555: |
delete[ ] d3; | delete[] d3; |
行号 305: | 行号 569: |
== 对象的复制 == 对象在两种情况下发生复制:赋值和拷贝构造。拷贝构造是指创建一个对象,创建的同时让它的值与另一个已存在的对象一模一样。赋值是复制另一个同类型的值给已存在的对象,使它变成新的值。比如 {{{#!cplusplus void f( Date d ); int main() { Date today; //默认构造函数 Date d = today; //拷贝构造 Date s( today ); //拷贝构造 d = another_day; //赋值 f(s); //拷贝构造 |
== 对象的拷贝构造 == 对象的复制分为两种情况:赋值和拷贝构造。拷贝构造是指创建一个新对象,创建的同时让新对象的值与另一个已存在的对象一模一样。赋值是复制一个对象的值给另一个已存在的对象,使两个对象的值一样。比如 {{{#!cplusplus void display( Clock d ) { //... } int main() { Clock now; //默认构造函数 Clock d = now; //拷贝构造 Clock s( now ); //拷贝构造 d = s; //赋值 display(s); //拷贝构造 |
行号 320: | 行号 587: |
拷贝构造的工作由拷贝构造函数完成。如果自己没有编写拷贝构造函数,编译器会自动生成一个拷贝构造函数,用于完成缺省的拷贝构造的功能:用源对象的所有数据成员逐一拷贝构造目标对象的相应成员。我们可以自定义拷贝构造函数去改写默认的拷贝构造函数。 | 拷贝构造的工作由一个特殊的构造函数拷贝构造函数完成。如果一个类中没有编写拷贝构造函数,编译器会自动生成一个拷贝构造函数,用于完成默认的拷贝构造的功能:用源对象的所有数据成员逐一拷贝构造目标对象的相应成员。我们可以自定义拷贝构造函数去改写默认的拷贝构造函数。比如: |
行号 325: | 行号 592: |
Clock(Clock& c) { | Clock(int h, int m, int s) { hour = h; minute = m; second = s; } //这个拷贝构造函数实现的功能与编译器自动声成的相同,可以省略 Clock(Clock& c) { |
行号 327: | 行号 600: |
minite = c.minute; second = 0; } }; }}} 何时需要自定义拷贝构造函数、析构函数?当涉及到内存管理时,一个类中有指针成员,指向动态分配的空间,那么通常需要写一组拷贝构造函数、析构函数和赋值操作符。写一个类Array,模拟数组的功能,元素是int类型,数组的大小可以是变量,增加检查越界的功能。即可以这样使用: |
minute = c.minute; second = c.second; } }; int main() { Clock c; Clock d(c); //拷贝构造d,其值与c一样。 } }}} 何时需要自定义拷贝构造函数、析构函数?当涉及到内存管理时,一个类中有指针成员,指向动态分配的空间,那么通常需要写一组拷贝构造函数、析构函数和赋值操作符。例如: {{{#!cplusplus class String{ char *s; public: String(char *str) { s = new char[strlen(str)+1]; strcpy(s, str); } ~String() { delete[] s; } String(String &str) { s = new char[strlen(str.s)+1]; strcpy(s, str.s); } }; int main() { String s("Hello"); String s2(s); } }}} 再例如:写一个类Array,模拟数组的功能,元素是int类型,数组的大小可以是变量,增加检查越界的功能。即可以这样使用: |
行号 400: | 行号 703: |
== 类的组合 == 构造复杂的对象的一种方法是组合。一个类可以使用另一个类的对象作为成员,比如: {{{#!cplusplus class point{ private: double x, y; public: //... }; class line { private: point start, end; public: //... }; class circle { private: point center; double radius; public: //... }; class rectangle { private: point p1, p2; public: //... }; }}} 组合对象的构造 {{{#!cplusplus class point{ double x, y; public: point(double x0, double y0) { x = x0; y = y0; } }; class line { point start, end; public: line(double x0, double y0, double x1, double y1) { start.x = x0; start.y = y0; end.x = x1; end.y = y1; } }; }}} 这个程序遇到编译错误。一是因为point类的x,y是私有的,在line类的构造函数中访问point的x,y是错误的。二是构造line中内含的两个point对象start,end需要参数,而在line的构造函数中没有给出这些参数。解决办法:使用构造函数初始化列表 {{{#!cplusplus class point{ private: double x, y; public: point(double x0, double y0) { x = x0; y = y0; cout << "point " << x << y << "initialized!" << endl; } }; class line { private: point start, end; public: line(double x0, double y0, double x1, double y1) : start(x0, y0), end(x1, y1) { cout << "line initializing" << endl; } }; int main() { line l(1,2,3,4); } }}} 在line构造函数的初始化列表中,给start、end成员的构造函数传递参数完成它们的初始化。 如果内嵌的类有默认构造函数,则初始化列表可以省略。 {{{#!cplusplus class point{ private: double x, y; public: point(double x0 = 0, double y0 = 0) { x = x0; y = y0; cout << "point " << x << y << "initialized!" << endl; } }; class line { private: point start, end; public: line(double x0, double y0, double x1, double y1) { cout << "line initializing" << endl; } }; int main() { line l(1,2,3,4); } }}} 初始化表还能够解决其它一些类型的成员的初始化问题,比如const成员,引用成员等。比如 {{{#!cplusplus class C { public: C(int h, int m, int s) : s ( 0 ), x( 0 ), time(h, m, s), t(time) { } private: int x; const int s; //const data member Clock time; // no default construct Clock &t; // reference member }; }}} 构造函数和析构函数执行的顺序 {{{#!cplusplus class point{ private: double x, y; public: point(double a, double b) :x(a), y(b) { cout << "construct point" << endl; } ~point() { cout << "destruct point" << endl; } }; class line { private: point start, end; public: line(double x0, double y0, double x1, double y1) : start(x0, y0) , end(x1, y1) { cout << "construct line" << endl; } ~line() { cout << "destruct line" << endl; } }; }}} == 静态成员 == 类的一般数据成员,每个对象都有这样一组数据成员,它们的空间是独立的。C++的类还有一种特殊的成员,它的空间是这个类的所有对象共享的,称为类静态数据成员 {{{#!cplusplus class Task { public: static unsigned n; //声明静态成员n int m; }; unsigned Task::n; //定义静态成员n int main() { Task t1, t2; t1.n = 10; cout << t2.n; } }}} 这里n的空间在整个程序里面只有一个,t1,t2共享同一个n。 静态数据成员需要声明和定义。初始化不是在构造函数中进行,而是在定义的时候进行。访问静态数据成员可以和一般成员一样,通过对象名来访问,也可以不通过对象直接用类名来访问。 {{{#!cplusplus class Task { public: Task() : x(n) { n++; } ~Task() { n--; } int x; static unsigned n; }; unsigned Task::n = 1;// 初始化 int main() { cout << Task::n << endl; Task s1; cout << s1.x << s1.n << endl; Task s2; cout << s1.x << s1.n << endl; cout << s2.x << s2.n << endl; cout << Task::n << endl; } }}} 静态成员函数:只能访问静态成员(包括静态数据成员和静态成员函数)的成员函数 {{{#!cplusplus class Task { private: static unsigned n; public: static unsigned get_count() { return n; } }; unsigned Task::n = 0; int main() { cout << Task::get_count(); Task t; cout << t.get_count(); } }}} 静态成员用法举例:写一个类Singleton,这个类只能存在一个对象。 {{{#!cplusplus class Singleton{ public: static Singleton &instance() { static Singleton s; return s; } private: Singleton() { /*...*/ } Singleton(Singleton&){ /*...*/ } }; }}} == 友元 == 友元提供了让其它函数或类直接访问私有成员的途径。友元函数是能够直接访问类的私有成员的函数。 {{{#!cplusplus 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; } int main() { Point a, b; // initialize cout << isequal(a, b); } }}} 友元类 {{{#!cplusplus class Point { int x, y; friend class Rectangle; // Rectangle是Point的友元类 }; class Rectangle { Point p1, p2; public: void display() { cout << p1.x << p1.y << p2.x << p2.y; } }; int main() { Rectangle rect; // initialize rect.display(); } }}} 友元关系是单向的,并不可传递。 == const对象 == 定义对象前面加const,表示对象不能改变 {{{#!cplusplus class Clock{ int hour, minute, second; public: Clock(int h, int m, int s) : hour(h), minute(m), second(s) { } void SetTime(int h, int m, int s) { hour = h; minute = m; second = s; } void ShowTime() { cout << hour << minute << second; } }; int main() { const int i = 10; cout << i*10; // ok i++; // error! const Clock t(10, 30, 20); cout << t.second; // ok t.second = 10; // error t.SetTime(10, 20, 10); // error! t.ShowTime(); // error! } }}} 成员函数声明后面加const,表示这个成员函数不修改数据成员。这样的函数可以被const对象调用。 {{{#!cplusplus class Clock{ int hour, minute, second; public: Clock(int h, int m, int s) : hour(h), minute(m), second(s) { } void SetTime(int h, int m, int s) { hour = h; minute = m; second = s; } void ShowTime() const { cout << hour << minute << second; } }; int main() { const int i = 10; cout << i*10; // ok i++; // error! const Clock t(10, 30, 20); cout << t.second; // ok t.second = 10; // error t.SetTime(10, 20, 10); // error! t.ShowTime(); // ok } }}} 常成员函数可以被常对象调用,也可以被普通对象调用。 {{{#!cplusplus int main() { const Clock time(10, 20, 30); time.ShowTime(); Clock now(10, 10, 10); time.ShowTime(); } }}} 常成员函数与非常成员函数可以重载 {{{#!cplusplus class Array { private: int *p, 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 throw out_of_range(); } int at( int i) const { if( i < size) return p[i]; else throw out_of_range(); } }; int main() { Array a(10); a.at(5) = 5; const Array b(5); b.at(5) = 5; // error } }}} == this指针 == {{{#!cplusplus class Clock { int second, minute, hour; public: Clock(int h, int m, int s) : hour(h), minute(m), second(s) { } void ShowTime() { cout << second << minute << hour; //cout << this->second << this->minute << this->hour; } }; int main() { Clock t1(10, 20, 30), t2(20, 30, 10); t1.ShowTime(); t2.ShowTime(); } }}} {{{#!cplusplus class Clock { public: Clock &set_second(int n); Clock &set_minute(int n); Clock &set_hour(int n); }; Clock &Clock::set_second(int n) { second = n; return *this; } int main() { Clock time(1999, 9, 9); time.set_second(20); time.set_minute(30) time.set_hour(10); time.set_second(20).set_minute(30).set_hour(10); } }}} this有两种类型:以Clock类为例,Clock* const和const Clock* const {{{#!cplusplus class Clock { public: void ShowTime() const { cout << second << minute << hour; second++; //error this类型为const Date *this Clock *p = (Clock*)(this); // 或者Clock *p = const_cast<Clock*>(this); p->second++; //ok } }; }}} = The End = |
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. 对象的拷贝构造
对象的复制分为两种情况:赋值和拷贝构造。拷贝构造是指创建一个新对象,创建的同时让新对象的值与另一个已存在的对象一模一样。赋值是复制一个对象的值给另一个已存在的对象,使两个对象的值一样。比如
拷贝构造的工作由一个特殊的构造函数拷贝构造函数完成。如果一个类中没有编写拷贝构造函数,编译器会自动生成一个拷贝构造函数,用于完成默认的拷贝构造的功能:用源对象的所有数据成员逐一拷贝构造目标对象的相应成员。我们可以自定义拷贝构造函数去改写默认的拷贝构造函数。比如:
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 //这个拷贝构造函数实现的功能与编译器自动声成的相同,可以省略
10 Clock(Clock& c) {
11 hour = c.hour;
12 minute = c.minute;
13 second = c.second;
14 }
15 };
16
17 int main() {
18 Clock c;
19 Clock d(c); //拷贝构造d,其值与c一样。
20 }
何时需要自定义拷贝构造函数、析构函数?当涉及到内存管理时,一个类中有指针成员,指向动态分配的空间,那么通常需要写一组拷贝构造函数、析构函数和赋值操作符。例如:
再例如:写一个类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 };
组合对象的构造
这个程序遇到编译错误。一是因为point类的x,y是私有的,在line类的构造函数中访问point的x,y是错误的。二是构造line中内含的两个point对象start,end需要参数,而在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 };
21 int main() {
22 line l(1,2,3,4);
23 }
在line构造函数的初始化列表中,给start、end成员的构造函数传递参数完成它们的初始化。
如果内嵌的类有默认构造函数,则初始化列表可以省略。
1 class point{
2 private:
3 double x, y;
4 public:
5 point(double x0 = 0, double y0 = 0) {
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 cout << "line initializing" << endl;
18 }
19 };
20
21 int main() {
22 line l(1,2,3,4);
23 }
初始化表还能够解决其它一些类型的成员的初始化问题,比如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++的类还有一种特殊的成员,它的空间是这个类的所有对象共享的,称为类静态数据成员
这里n的空间在整个程序里面只有一个,t1,t2共享同一个n。
静态数据成员需要声明和定义。初始化不是在构造函数中进行,而是在定义的时候进行。访问静态数据成员可以和一般成员一样,通过对象名来访问,也可以不通过对象直接用类名来访问。
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 = 1;// 初始化
9
10 int main() {
11 cout << Task::n << endl;
12 Task s1;
13 cout << s1.x << s1.n << endl;
14 Task s2;
15 cout << s1.x << s1.n << endl;
16 cout << s2.x << s2.n << endl;
17 cout << Task::n << endl;
18 }
静态成员函数:只能访问静态成员(包括静态数据成员和静态成员函数)的成员函数
静态成员用法举例:写一个类Singleton,这个类只能存在一个对象。
8. 友元
友元提供了让其它函数或类直接访问私有成员的途径。友元函数是能够直接访问类的私有成员的函数。
友元类
友元关系是单向的,并不可传递。
9. 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 cout << i*10; // ok
20 i++; // error!
21 const Clock t(10, 30, 20);
22 cout << t.second; // ok
23 t.second = 10; // error
24 t.SetTime(10, 20, 10); // error!
25 t.ShowTime(); // error!
26 }
成员函数声明后面加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() const {
14 cout << hour << minute << second;
15 }
16 };
17 int main() {
18 const int i = 10;
19 cout << i*10; // ok
20 i++; // error!
21 const Clock t(10, 30, 20);
22 cout << t.second; // ok
23 t.second = 10; // error
24 t.SetTime(10, 20, 10); // error!
25 t.ShowTime(); // ok
26 }
常成员函数可以被常对象调用,也可以被普通对象调用。
常成员函数与非常成员函数可以重载
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 }
10. this指针
1 class Clock {
2 int second, minute, hour;
3 public:
4 Clock(int h, int m, int s)
5 : hour(h), minute(m), second(s)
6 {
7 }
8 void ShowTime() {
9 cout << second << minute << hour;
10 //cout << this->second << this->minute << this->hour;
11 }
12 };
13 int main() {
14 Clock t1(10, 20, 30), t2(20, 30, 10);
15 t1.ShowTime();
16 t2.ShowTime();
17 }
1 class Clock {
2 public:
3 Clock &set_second(int n);
4 Clock &set_minute(int n);
5 Clock &set_hour(int n);
6 };
7 Clock &Clock::set_second(int n) {
8 second = n;
9 return *this;
10 }
11 int main() {
12 Clock time(1999, 9, 9);
13 time.set_second(20);
14 time.set_minute(30)
15 time.set_hour(10);
16 time.set_second(20).set_minute(30).set_hour(10);
17 }
this有两种类型:以Clock类为例,Clock* const和const Clock* const