类
C++使用struct、class来定义一个类:struct的默认成员权限是public,class的默认成员权限是private;除此之外,二者基本无差别。
1 2 3 4 5 6 7 8 9 class Student {private : string name; double score; public : double GetSorce () { return score; } };
上面学生的类并不是真实世界中的学生,只是一个抽象的概念,并不包含真实世界中学生的所有属性,只是把一些属性抽象出来。
面向对象的误区:对象是对现实世界中具体物体的反映,继承是对物体分类的反映?这个观念是错误的。举个例子,现实生活中我们往往把正方形看作是长和宽都相等的特殊的长方形,如果把这个思想引入到C++中,可能会这么设计继承关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class rectangle { virtual void SetLength (double a) { …… } }; class square : public rectangle { virtual void SetLength (double a) { …… } }
上面设计了两个类,一个长方形类,一个正方形类,其中正方形类继承了长方形类以及内部的方法SetLength。当我们调用长方形类SetLength的方法时,我们只是修改了长方形的长,长方形的宽不受影响;但是如果我们调用了正方形对象的SetLength方法,不仅长会受到影响,宽也会受到影响。这个从面向对象的继承体系来说就有很大的问题了。所以我们不要把现实世界中的关系代入到面向对象编程中。
抽象法则
具体类型的抽象
一个普通的int型变量,可以完成加、减、乘、除、比较、输出、自增等等一系列操作;如果现在有一个自定义的复数类型,我们自然也希望可以像使用int型变量一样使用它,同时它对我们是一个黑盒,一种抽象,我们不需要关心内部是如何实现的。
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 57 58 59 60 #pragma once #include <iostream> using namespace std;class Complex { public : Complex (); Complex (double r, double i); virtual ~Complex (); Complex (const Complex& x); Complex& operator =(const Complex &c); double GetReal ( ) const { return _real; } void SetReal (double d) { _real = d; } double GetImage () const { return _image; } void SetImage (double i) { _image = i; } Complex operator +(const Complex &c) const ; Complex& operator +=(const Complex &c); Complex operator -(const Complex &c) const ; Complex& operator -=(const Complex &c); Complex operator *(const Complex &c) const ; Complex& operator *=(const Complex &c); Complex operator /(const Complex &c) const ; Complex& operator /=(const Complex &c); Complex& operator =(const Complex &c); bool operator ==(const Complex &c) const ; bool operator !=(const Complex &c) const ; bool operator >(const Complex &c) const ; bool operator >=(const Complex &c) const ; bool operator <(const Complex &c) const ; bool operator <=(const Complex &c) const ; Complex& operator ++(); Complex operator ++(int ); Complex& operator --(); Complex operator --(int ); friend ostream& operator <<(ostream& os, const Complex &x); friend istream& operator >>(istream& is, Complex &x); private : double _real; double _image; };
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 #include "stdafx.h" #include "complex.h" Complex::Complex () { _real = 0.0 ; _image = 0.0 ; } Complex::Complex (double r, double i) { _real = r; _image = i; } Complex::Complex (const Complex& c) { _real = c._real; _image = c._image; } Complex& Complex::operator = (const Complex& c) { if (this != &c) { _real = c._real; _image = c._image; } return *this ; } Complex::~Complex () { _real = _image = 0.0 ; } Complex Complex::operator + (const Complex& c) const { return Complex (_real + c._real, _image + c._image); } Complex& Complex::operator += (const Complex& c) { _real += c._real; _image += c._image; return *this ; } Complex Complex::operator -(const Complex &c) const { return Complex (_real - c._real, _image - c._image); } Complex& Complex::operator -=(const Complex &c) { _real -= c._real; _image -= c._image; return *this ; } Complex Complex::operator *(const Complex &c) const { return Complex (_real*c._real - _image*c._image, _real*c._image + _image*c._real); } Complex& Complex::operator *=(const Complex &c) { Complex tmp (*this ); _real = tmp._real*c._real - _image*c._image; _image = tmp._real*c._image + tmp._image*c._real; return *this ; } Complex Complex::operator /(const Complex &c) const { double t = c._real*c._real + c._image*c._image; return Complex ((_real*c._real - _image*(-c._image)) / t, (_real*(-c._image) + _image*c._real) / t); } Complex& Complex::operator /=(const Complex &c) { Complex tmp (*this ); double t = c._real*c._real + c._image*c._image; _real = (tmp._real*c._real - tmp._image*(-c._image)) / t; _image = (tmp._real*(-c._image) + tmp._image*c._real) / t; return *this ; } bool Complex::operator ==(const Complex& c) const { return (_real == c._real) && (_image == c._image); } bool Complex::operator !=(const Complex& c) const { return !( (_real == c._real) && (_image == c._image) ); } bool Complex::operator >(const Complex &c) const { return (_real > c._real) && (_image > c._image); } bool Complex::operator >=(const Complex &c) const { return (_real >= c._real) && (_image >= c._image); } bool Complex::operator <(const Complex &c) const { return (_real < c._real) && (_image < c._image); } bool Complex::operator <=(const Complex &c) const { return (_real <= c._real) && (_image <= c._image); } Complex& Complex::operator ++ () { _real++; _image++; return *this ; } Complex Complex::operator ++ (int ) { return Complex (_real++, _image++); } Complex& Complex::operator --() { _real--; _image--; return *this ; } Complex Complex::operator --(int ) { return Complex (_real--, _image--); } ostream& operator <<(ostream& os, const Complex &x) { os << "real value is " << x._real << " image value is " << x._image; return os; } istream& operator >> (istream& is, Complex &x) { is >> x._real >> x._image; return is; }
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 int main () { Complex a (1.0 , 2.0 ) ; cout << a.GetReal () << endl; cout << a.GetImage () << endl; a.SetImage (2.0 ); a.SetReal (3.0 ); cout << a.GetReal () << endl; cout << a.GetImage () << endl; Complex a (3.0 , 2.0 ) ; Complex c; c = a + b; Complex d; d = c++; cout << d << endl; cin >> d; cout << d << endl; return 0 ; }
类中有个this指针,指向当前对象本身。
类创建后会系统默认创建一个构造函数,我们可以自己实现构造函数。但我们如果重写了构造函数,那么原始的默认构造函数就不存在了,如果想使用需要重新声明实现。
等号运算符也一样,系统会默认帮我们重载。不过我们最好不要过于相信系统默认的重载,在复杂情况下运算的结果可能不是我们想要的。
程序中的临时对象一定要注意优化,避免产生临时对象,否则会触发拷贝构造。
抽象类型的抽象
数学中有不同的图形,比如长方形、原型、三角形;多种图形计算周长、面积的方法不同,但都需要一个计算方法。我们可以抽象出一个图形类Shape,用Shape类进行公共层面的抽象操作。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include "stdafx.h" #include <iostream> using namespace std;class Shape { public : virtual double Area () const = 0 ; virtual void Show () = 0 ; void SetColor (int color) { _color = color; } void Display () { cout << Area () << endl; } private : int _color; }; class Square : public Shape{ public : Square (double len) :_len(len) { } void Show () { cout << "Square" << endl; } double Area () const { return _len*_len; } private : double _len; }; class Circle : public Shape{ public : Circle (double radius) :_radius(radius) {} void Show () { cout << "Circle" << endl; } double Area () const { return 3.1415926 *_radius*_radius; } private : double _radius; }; class Triangle : public Shape{ public : Triangle (double len, double height) :_len(len), _height(height){} void Show () { cout << "Triangle" << endl; } double Area () const { return 0.5 *_len*_height; } private : double _len; double _height; }; int main () { const int shapeNum = 3 ; Square s1 (2.0 ) ; s1.SetColor (1 ); Circle c1 (2.0 ) ; Triangle t1 (2.0 , 3.0 ) ; Shape* shapes[shapeNum]; shapes[0 ] = &s1; shapes[1 ] = &c1; shapes[2 ] = &t1; for (unsigned int index = 0 ; index < shapeNum; index++) { shapes[index]->Display (); } cout << endl; cout << sizeof (s1) << endl; return 0 ; }
对象模型和虚函数
C++的对象模型中,子类对象中包含了父类。父类中有一个虚函数列表,是个类似数组的结构。对象模型中只保留成员变量信息和虚函数列表,其他的共有函数是通过this指针来访问的。
深拷贝、浅拷贝、写时复制
浅拷贝:只拷贝指针地址,C++默认拷贝构造函数与赋值运算符重载都是浅拷贝;节省空间,但容易引发多次释放;
深拷贝:重新分配堆内存,拷贝指针指向内容;浪费空间,但是不会导致多次释放;
深拷贝的思想比较常见,比如C++的一个优化策略叫写时复制 。有个信息存放在内存空间中,如果大家都去读取,那么内存中保留一份即可,但如果有地方需要写数据,那么会复制出一个新的地址空间存放相同数据,写操作作用在新地址空间上。
深拷贝和浅拷贝各有优劣,如果想兼有二者的优点,有两种可用方案:第一是使用引用计数,用shared_ptr的思路,每有一个指针指向对象,引用计数+1,直到引用计数清零时再清理内存;第二种是C++11的新标准移动语义move,把资源让渡,既可以避免重新创建空间,也防止空间释放导致新问题。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 class String { public : String (const char *str = NULL ); String (const String &other); String (String&& other); ~String (void ); String& operator = (const String& other); String& operator =(String&& rhs)noexcept ; friend ostream& operator <<(ostream& os, const String &c); private : char *m_data; }; String::String (const char *str) { if (str == NULL ) { m_data = new char [1 ]; if (m_data != NULL ) { *m_data = '\0' ; } else { exit (-1 ); } } else { int len = strlen (str); m_data = new char [len + 1 ]; if (m_data != NULL ) { strcpy (m_data, str); } else { exit (-1 ); } } } String::String (const String &other) { int len = strlen (other.m_data); m_data = new char [len + 1 ]; if (m_data != NULL ) { strcpy (m_data, other.m_data); } else { exit (-1 ); } } String::String (String&& other) { if (other.m_data != NULL ) { m_data = other.m_data; other.m_data = NULL ; } } String& String::operator = (const String &other) { if (this == &other) { return *this ; } delete [] m_data; int len = strlen (other.m_data); m_data = new char [len + 1 ]; if (m_data != NULL ) { strcpy (m_data, other.m_data); } else { exit (-1 ); } return *this ; } String& String::operator =(String&& rhs)noexcept { if (this != &rhs) { delete [] m_data; m_data = rhs.m_data; rhs.m_data = NULL ; } return *this ; } String::~String (void ) { if (m_data != NULL ) { delete [] m_data; } } ostream& operator <<(ostream& os, const String &c) { os << c.m_data; return os; } int main () { String s1 ("Hello" ) ; cout << s1 << endl; String s2 (s1) ; cout << s2 << endl; String s2A (std::move(s1)) ; cout << s2A << endl; String s3; cout << s3 << endl; s3 = s2; cout << s3 << endl; String s3A; s3A = std::move (s2A); cout << s3A << endl; return 0 ; }
面向对象三大特性
封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问;封装可以使得代码模块化;
继承性:让某种类型对象获得另一个类型对象的属性和方法,继承可以扩展已经存在的代码;
多态性:同一事物在保有原来特点的情况下表现出不同事物的能力,即不同对象会产生不同的行为;多态的目的是为了接口重用;
面向对象是软件工程发展到一定阶段为了管理代码和数据提出的一种方法,它没有解决以前解决不了的问题,不是万能的,只是为我们便捷的开发出能适应快速变化的软件提供了可能。面向对象不是对现实世界的映射,但它的封装性可以把问题简化;它的继承性可以减少代码重复,避免重新发明轮子;它的多态可以实现灵活的功能扩充,提升开发效率;