
本文共 6194 字,大约阅读时间需要 20 分钟。
Essential C++ 第四章
1. 类的组成
通常一个类的基本组成元素包括成员变量和成员函数。按照权限进行分类的话,可以分为private和public两种。public的成员,意味着可以通过任何方式访问这些成员;private意味着,只有在类中才能访问这些成员。
例如:
class myClass { public: int _b; void print() { cout << _a << endl; } private: int _a;};
在上面的类中,_b是public的,所以myClass的实例cls可以通过类访问符进行直接访问;而_a是private的,不能通过实例cls进行直接访问,因为private的对象只能由类的成员进行访问,比如其成员函数print可以访问。
值得注意的一点是,如果定义类成员的时候,前面不写public和private,编译器会默认这些成员是private的。
2. 类的构造
构造函数和析构函数是一类特殊的成员函数。构造函数是类进行定义的时候进行调用的,用来初始化类中的变量;析构函数在类使用完毕后进行调用,用于清空构造这个类时所申请的全部内存空间。
构造函数和析构函数都是没有返回值的函数,并且与类是同名的。
例如:
class myClass { public: myClass(int a) { _a = a; } private: int _a;};
如果你没有定义构造函数和析构函数,编译器有默认的构造函数和析构函数,他们没有任何输入参数。
2.1 构造函数
构造函数主要是用来给类赋予初值的,有函数体内赋值和初始化列表赋值两种,同时也可以像非类成员函数那样具有默认值。
- 函数体内赋值:
class myClass { public: myClass(int a) { _a = a; } private: int _a;};
- 初始化列表:
class myClass { public: myClass(int a, int b) : _a(a), _b(b) { } private: int _a; int _b;};
- 带有默认值的构造函数:
class myClass { public: myClass(int a = 1, int b = 1) : _a(a), _b(b) { } private: int _a; int _b;};
2.2 析构函数
析构函数前面具有一个~,也没有返回值。
例如:
~myClass() {}
2.3 拷贝构造
在进行类创建的时候,往往会需要以原有的实例创建新的实例对象。例如:
class myClass { public: myClass(int a) : _a(a) { } private: int _a;};myClass a(1);myClass b = a; // 拷贝构造
在第一章的内容中,我们知道以下两种对变量的定义方法是等效的:
int a = 1;int a(1);
对类的拷贝来说,也是一样的。myClass b = a和myClass b(a)是等价的。myClass b(a)这种写法与构造函数非常相似,但是我们定义的构造函数并不接受myClass这种类型的参数输入,那么对象b到底是怎么被构造的呢?
原来,C++中,有种默认的拷贝构造函数,能够接受同类对象。因此,实际上b(a)这种写法,调用的不是构造函数,而是默认拷贝构造函数,默认拷贝构造函数会在下一个小节进行讲述。
2.4 拷贝构造(继续)
默认拷贝构造函数会进行一一复制操作。如果一个数据变量是一个指针类型的话,拷贝构造之后,会使得两个变量指向同一个地址。如果你的类中,涉及到地址的话,复制的时候就要格外小心了,以防内存访问错误问题。
例如:
class myClass { public: myClass() { int *p = new int[3]; } private: int *p;};int main() { myClass a; myClass b(a);}
这种情况下使用默认的拷贝构造函数就是一种非常危险的情况,因为new定义的变量是单独的,必须通过delete才能够使电脑释放这部分内存,而经过默认的拷贝构造后,a和b的变量p指向了同一块内存,第一个析构函数调用时,会清空这个内存。第二个析构函数调用时,又会来清空这块内存,但是这块内存以及刚刚被清除了,于是就会发生内存访问错误,为了避免这种情况的发生,必须要使用自定义的拷贝构造函数。
2.5 自定义拷贝构造函数
自定义拷贝构造函数,就是定义一个输入类型为自身的构造函数。
例如:
class myClass { public: myClass() { int *p = new int[3]; } myClass(myClass m) { int *p = new int[3]; for (int i = 0; i < 3; i++) { this->p[i] = m.p[i]; } } private: int *p;};int main() { myClass a; myClass b(a);}
3. 类成员的管理
3.1 通过const和mutable进行类修改权限管理
- const:当你定义一个成员函数时,如果这个成员函数并没有改变对象内的任何一个数据时,就可以把它设置为const,const意味着这个类不能对对象作出修改。
例如:
#includeusing namespace std;class myClass { public: myClass(int a) : _a(a) { } void print() const { cout << _a << endl; } void a_plus() { _a++; } private: int _a;};int main() { myClass cls(1); cls.print(); cls.a_plus();}
- mutable:是针对变量来说的,可以让const函数修改类中的mutable变量。
例如:
#includeusing namespace std;class myClass { public: myClass(int a) : _a(a) { } void print() const { cout << _a << endl; } void a_plus() const { _a++; } private: mutable int _a;};void f(const myClass &cls) { cls.a_plus(); cls.print();}int main() { myClass cls(1); f(cls);}
3.2 通过静态进行内存管理
静态变量的特性:
- 隐藏性:在这个cpp文件里面定义的变量,其他cpp文件不能调用。
- 永久性:静态变量在main函数运行之前被定义好,只能发生一次初始化,并且全局有效。
静态的成员变量:
- 类的所有对象都能够使用这个变量,并且这个变量值是固定的。
- 初始化通常在类外面进行。
例如:
#includeusing namespace std;class A { public: static int i; static void f() { cout << i << endl; } private: static int i;};int A::i;int main() { A a; a.i = 10; A b; cout << b.i << endl; cout << A::i << endl; A::f(); return 0;}
- 静态的成员函数:只能通过类名进行访问,内部不能使用this指针。
3.3 通过友元进行访问权限管理
友元可以为某个函数或类赋予访问私有成员的权限。
例如:
#includeusing namespace std;class myClass { public: friend class ClassB; friend void f(myClass A); private: int _a;};class ClassB { public: void f(myClass A) { cout << A._a << endl; }};void f(myClass A) { cout << A._a << endl;}int main() { myClass A(1); ClassB b; b.f(A); f(A);}
3.4 this指针
this指针指代的就是类定义的实例其本身,可以用来返回类自己。
4. 运算符重载
运算符重载本质就是一种特殊的成员函数,允许自定义类进行特定的运算。
4.1 运算符重载的规则
- 不能改变运算符的变量个数。
- 不能创造新的运算符。
- 运算符的变量中,必须出现类。
- 运算符的优先级不能改变。
4.2 双目运算符的重载
- 在类外重载运算符:
int operator+(myClass &A, myClass &B) { return A._a + B._a;}
- 在类内重载运算符:
class myClass { public: myClass(int a) : _a(a) { } int _a; int operator+(myClass &B) { return _a + B._a; }};
4.3 单目运算符的重载
- 运算符++:
class myClass { public: myClass &operator++() { } myClass operator++(int) { }};
4.4 提领运算符的重载
int operator*() const {}
4.5 函数调用符的重载(函数对象)
函数对象用于STL算法中的函数调用。
4.6 赋值符的重载
class Matrix { public: Matrix &operator=(const Matrix &mat) { if (this != &mat) { this->_row = mat->_row; this->_col = mat->_col; delete[] _pmat; _pmat = new double[elem_cnt]; for (int i = 0; i < elem_cnt; i++) { _pmat[i] = mat._pmat[i]; } } return *this; } private: int _row; int _col; double *_pmat; int elem_cnt;};
4.7 iostream的重载
- 定义在类外:
class myClass { public: int length;};ostream &operator<<(ostream &os, myClass &rhs) { os << rhs.length << endl; return os;}
- 使用方法:
myClass cls;cout << cls << endl;
- 另一种定义在类外的方法:
class myClass { public: int length;};ostream &operator<<(myClass &rhs, ostream &os) { os << rhs.length << endl; return os;}
- 定义在类内:
class myClass { public: ostream &operator<<(ostream &os) { os << length << endl; return os; } int length;};
5. 指针—指向类成员函数的指针
指向类成员的指针和指向普通函数的指针比较相似。例如:
class myClass { public: void f() { cout << "1" << endl; } typedef void (myClass::* ptr)(); private:};typedef myClass::ptr ptr;int main() { myClass cls; ptr p = &myClass::f; (cls.*p)(); return 0;}
发表评论
最新留言
关于作者
