第 12 章 类
1、构造函数
构造函数的定义比较简单的话还是放在类内部,如
- Sales_Item() : a(0),
- b(0) {};
- sales_Item(int c) : a(c),
- b(0) {};
- //在不允许修改对象的数据成员时,用const但是具体还有另外的使用情况,忘了,这应该是最常用的情况吧。
- int fuc() const;
- //声明和定义中都必须要有const,但是<strong>内联函数</strong>可以不需要,只要在内部或者外部加上就可以。
2、数据抽象和封装的好处?
在使用类方面是抽象的,只需要考虑能执行的操作就可以。而无法知道其内部表示,所以是封装的。
有两个好处:
(1)避免用户的错误使用破坏对象的状态。
(2)对以后在完善类的实现时,不需要改变用户级的代码。
3、std::string ?
如
std::string::size_type
4、使用类型别名来简化类
在类的内部,如使用别的类,而且比较长得时候,可以使用关键字 typedef 来简化类
typedef std::string::size_type index;
5、显示指定 inline 函数
若成员函数没有实参,则默认为内联函数,当然也可以指定为内联函数。
6、为什么类的定义以分号结束?
方便在定义完成之后直接定义对象。
7、隐含的 this 指针
(1) 使用场合
this 隐含在成员函数之中,和调用成员函数的对象绑定在一起,有的时候需要返回对象的时候有用,如:
- a.b().c();
a 为对象,b() 函数需要返回对象
(2)从 const 成员函数返回 * this
这样是无法做到的,需要重载
测试:
- const & Screen b() const {
- return * this;
- }
- //和
- & Screen b() const {
- return * this;
- }
- //的区别 以及Screen&的情况,答案是&必须要放在后面,而缺必须要有两个const,正确代码如下
- #include "stdafx.h"class Screen {
- public: const Screen & b() const {
- printf("first call\n");
- return * this;
- }
- void c() {
- printf("second call\n");
- return;
- }
- Screen & a() {
- return * this;
- }
- }
- S;
- int _tmain(int argc, _TCHAR * argv[]) {
- S.b();
- S.a().c();
- return 0;
- }
8、可变数据成员
在有的 const 成员函数中,有的数据成员可以修改,另外是不允许修改的,这时就要用到这个。
如:
- public: mutable int a;
9、用于公共代码的 private 函数
这个函数里面一般放一些测试用的东西,但是具体怎样用,以后遇到了好的代码再学习吧。
10、构造函数初始化列表
这个的用处是,在对一些 const 成员,或者引用成员(?)或者没有默认构造函数的情况下(?),必须用初始化列表
相关测试代码如下:
- #include "stdafx.h"#include < iostream > using namespace std;
- class Screen {
- public: Screen(int i) : a(0),
- c(i) {
- b = 1;
- i = i + 1;
- c = c + 1;
- } //此处i对c没有影响,只用于c初始化有用,这里是形参
- const int a; int b; int & c; //引用的作用是相当于做了一个实参的副本,随之而变化
- };
- int _tmain(int argc, _TCHAR * argv[]) { int i = 1; Screen S(i); i = i + 1; //i和c都加1
- S.c = S.c + 1; //没有用
- cout << S.a << S.b << S.c << i; //输出0122,
- return 0;
- }掌握这种用法还是很有必要的。
另外没有构造函数也是可以的,说明不进行初始化。
这种引用容易出问题,这里还是不建议使用。
11、引用的有关问题
作用:
(1)修改实参的值
(2)不产生临时变量,节约系统资源
例子:
- class A {
- public: A() {
- cout << "ctor" << endl;
- }
- A(const A & a) {
- cout << "copy ctor" << endl;
- }~A() {
- cout << "destor" << endl;
- }
- void put() {
- cout << "test" << endl;
- }
- };
- void fun1(A a) {
- a.put();
- }
- void fun2(A & a) {
- a.put();
- }
- int _tmain(int argc, _TCHAR * argv[]) {
- A a;
- fun2(a);
- return 0;
- }
这样只构造一次,输出为:
ctor
test
destor
若实参为常量,要引用的话,必须要在前面加上引用,表示不修改原来常量的值,代码如下
- Screen(const string & a) {
- cout << a << '\n';
- }...Screen S("2");
12、使用默认构造函数
有一种情况要注意,当有几个构造函数,而且形参不一样,但是允许为空时,这样就无法调用默认构造函数,如:
- public: Screen(const string & a = "") {
- cout << a << '\n';
- }
- Screen() {
- cout << "default" << endl;
- }...Screen S("2");
- Screen S1 = Screen(); //错误,无法确认是哪一个构造函数
- Screen S2; //错误
13、友元
见
14、Static 类成员
特点:
(1)是类的组成部分,但不是任何对象的组成部分,因此可以在类内定义其类型为类类型如 类 Bar 内可以定义为 static Bar mem; 而普通的成员
只能定义为 Bar *mem; 同样可以作为类内其它函数的实参。
(2)成员函数没有 this 指针,因此成员函数不能被声明为 const
(3)不能再类内初始化,但是 const static 若初始化值为常量则可以放在类内
(4)访问方式可以是类名:: 成员、对象:: 成员、对象指针:: 成员
1、复制构造函数
具体见
此文深入分析了复制构造函数应用的场合,以及深拷贝和浅拷贝,写的很好。
(1)如果不定义复制构造函数,编译器将合成一个
(2)深拷贝不简单拷贝了数据成员的值,而且拷贝了数据成员如指针所向空间的值,而浅拷贝则没有
(3)由于有些类禁止复制,因此,需要显示声明其复制构造函数为 private。但是友元类和函数还是可以复制。
13.4 消息处理示例(未看)
13.5 管理指针成员
(1)复制指针只复制指针中的地址,而不会复制指针对象
(2)可能一个指针删除了一个对象,但是另一指针的用户依然认为对象存在,造成错误。
(3)类中含有指针成员和一般成员,则,复制一个算术值时,副本独立于原版,可以改变一个副本而不改变另一个。
复制指针时,指针指向同一基础对象,如果在任意对象上调用,则二者的基础对象都会改变。@
13.5.1 定义智能指针类
计数的方式来实现智能指针,其实完全可以不必像书上的那样,利用一个 static 来实现即可,构造对象时,加 1,析构 - 1 当只剩最后一个时,释放指针即可。
先测试下,代码如下:
- #include "stdafx.h"#include < iostream > #include < string > using namespace std;
- class HasPtr {
- public: int * p;
- static int count;
- HasPtr(int * a) : p(a) {
- count++;
- }~HasPtr() {
- cout << "destor" << endl;
- if (--count == 0) {
- delete p;
- cout << "last endl" << endl;
- }
- }
- };
- int HasPtr: :count = 0;
- int _tmain(int argc, _TCHAR * argv[]) {
- int * p = new int[10];
- HasPtr Ptr(p);
- HasPtr Ptr1(p);
- return 0;
- }
成功运行,这里还学到了类中静态成员初始化的方法,必须在类外,但是至于为何要有 int 这个原因就不知道了(疑?)
STL 提供有 auto_ptr、unique_ptr、shared_ptr、weak_ptr
共同点,都有一个 explict 的构造函数,不允许隐式调用。
现在 auto_ptr 已经不用了,其原因是,不允许两个智能指针指向同一个对象,不方便,而且又没有限制。
(1)这样 unique_ptr 就做了限制,当另外一个 unique_ptr 是作为右值,也就是相当于是临时变量时,是可以的,以及接收以值返回的临时变量。而对于会存在一段时间的左值,是不允许的。
如
而对于 weak_ptr 是专门为 shared_ptr 准备的,可以对 shared_ptr 但是不改变其引用计数,当被观察的 shared_ptr 实效时,weak_ptr 也失效。
1、这三种都定义在 memory 头文件中
(1)重载操作符必须具有一个类类型操作数,这时避免改变内置类型的定义
(2)优先级和结合性是固定的,一般还是遵守 +-*& 的规则
(3)一般不重载 && || 操作符
(4)一般算术和关系操作符定义为非成员函数,而将赋值操作符定义为成员
(5)返回类型和左边形参一样
运算符重载形式有两种,重载为类的成员函数和重载为类的友元函数。
运算符重载为类的成员函数的一般语法形式为:
函数类型 operator 运算符(形参表)
{
函数体;
}
运算符重载为类的友元函数的一般语法形式为:
friend 函数类型 operator 运算符(形参表)
{
函数体;
}
例子:
- #include "stdafx.h"#include < iostream > #include < string > using namespace std;
- class complex {
- public: complex() {
- real = imag = 0;
- }
- complex(double r, double i) {
- cout << "cstor" << endl;
- real = r,
- imag = i;
- }
- complex operator += (const complex & c); //成员函数,一般是赋值操作
- friend complex operator + (const complex & a, const complex & c);
- public: double real,
- imag;
- };
- complex complex: :operator += (const complex & c) {
- real += c.real;
- imag += c.imag;
- return * this; //这里直接修改本对象的内容,比较方便
- //return complex(real + c.real, imag + c.imag); //用这句话的话,实现的功能和友元重载函数是一样的
- }
- complex operator + (const complex & a, const complex & c) //只能在类内声明friend
- {
- return complex(a.real + c.real, a.imag + c.imag);
- }
- int _tmain(int argc, _TCHAR * argv[]) {
- complex s1(1, 2);
- complex s2(1, 2);
- complex s3;
- s3 = s1 + s2;
- cout << s3.real << endl; //这里不改变s1的内容,s3为复制构造函数出来的。
- s1.operator += (s2); //要点 operator+=() 和成员函数的用法是一样的
- cout << s1.real << endl;
- return 0;
- }
输出结果为:
destor
destor
destor
2
2
注:复制操作成员函数可以和成员函数用法一样,如 s1.operator+=(s2),但是也可以方便的写成是 s1+=s2;
- using namespace std;
- class complex {
- public: friend ostream & operator << (ostream & os, const complex & c) //这里返回类型为什么一定要用&,??
- {
- os << c.a << endl;
- return os;
- }
- complex() : a(0) {}
- int a;
- };
输出结果为:0
同理,输入操作符重载为:
friend ostream& operator >>(ostream &os, complex &c)
这里 complex 就不是 const 类型啦 因为是输入
算术操作符和关系操作符用法差不多这里就不罗嗦了。
总结:
两种定义方式
实际用法
ostream 的一些东西要学习
- size_t size = sizeof(int);
- size_t val;注意:size_t存放的是整形
输出结果为 6 S=6 这样就实现了 S 和其它类型一样具有一样的操作
- #include "stdafx.h"#include < iostream > using namespace std;
- class SmallInt {
- public: SmallInt(int a) : val(a) {
- if (a > 10 || a < 0) cout << "out of range" << endl;
- }
- operator int() const {
- return val;
- }
- private: size_t val; //这里用int val 也是一样的,不过这个size_t的长度在不同的操作系统中是可变的
- };
- int _tmain(int argc, _TCHAR * argv[]) {
- SmallInt S(5);
- int a = S + 1;
- S = S + 1;
- cout << a << endl;
- cout << "S=" << S << endl;
- return 0;
- }
1、继承特点:
派生类具有原来的基本特性
具有自己的特性
具有自己的成员
注意:基类要想使派生类重新定义,那么必须声明为虚函数 virtual
问题:那么不声明可否?测试下
结论无论是否加上 virtual 输出都是:
- #include "stdafx.h"#include < iostream > using namespace std;
- class Food {
- public: void Des() {
- cout << "can eat" << endl;
- }
- }
- F;
- class Rice: public Food {
- public: void Des() {
- cout << "can eat and is white" << endl;
- } //或者在前面加上virtual
- }
- R;
- int _tmain(int argc, _TCHAR * argv[]) {
- F.Des();
- R.Des();
- return 0;
- }
can eat
can eat and is white
然而,并没有完,这并不是 virtual 的用法,其真正的用法是动态绑定,如下:
这种情况下,有 virtual 的输出为
- #include "stdafx.h"#include < iostream > using namespace std;
- class Food {
- public: virtual void Des() {
- cout << "can eat" << endl;
- }
- }
- F;
- class Rice: public Food {
- public: void Des() {
- cout << "can eat and is white" << endl;
- }
- }
- R;
- void printDes(Food & fd) //这里不能使用const 这是为何??还可以是指针,但是不能是Rice类,因为在主函数中无法从Food向Rice转化
- {
- fd.Des();
- return;
- }
- int _tmain(int argc, _TCHAR * argv[]) {
- printDes(F);
- printDes(R);
- return 0;
- }
can eat
can eat and is white
无 virtual 的输出为
can eat
can eat
这样加上 virtual 实现了动态绑定
原注解:在 c++ 中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用 (或指针) 既可以指向基类对象也可以指向派生类对象。
除构造函数外,任意非 static 函数都可以是 virtual 函数
派生类可以访问基类的 protected 成员,但是不能访问 private 成员
虚函数只对函数有作用,对于成员没有作用
2、public protected private 设计区别
若作为接口函数,供用户使用,那么定义为 pbulic
若为一般数据或成员,且希望派生类使用,那么为 protected
若不希望派生类使用,那么定义为 private(类本身和友元类还是可以使用)
3、派生类
可以指定多个基类
派生类重定义虚函数时,可以使用保留字 virtual,不过不使用也不影响,会默认为虚函数
声明类但是不定义时,只声明类名即可,不用: 继承列表之类的。
4、覆盖虚函数机制
有时需要使用原来基类中的成员函数版本,这样就需要覆盖机制,代码如下:
输出结果为:
- #include "stdafx.h"#include < iostream > using namespace std;
- class Food {
- public: virtual void Des() {
- cout << "can eat" << endl;
- }
- };
- class Rice: public Food {
- public: void Des() {
- cout << "can eat and is white" << endl;
- }
- };
- int _tmain(int argc, _TCHAR * argv[]) {
- Rice * R = new Rice;
- R - >Des();
- R - >Food: :Des(); //作用域
- delete R;
- return 0;
- }
can eat and is white
can eat
1、公用、私有和受保护的继承
共有 ---- 关系不变
受保护继承 ----public 变为 protected
私有继承 ---- 全部变成 private
(1)去除个别成员
如果说想大部分继承成员想要变成 private,而个别需要保持为 public, 则在派生类中加上:
这样 size 就变成了 public 了。
- class Derived: private base {
- pbulic: using base: :size;
- }
(2)默认继承保护级别
struct 默认保护继承级别为 public
class 默认保护级别为 private
如:
相当于:
- class Derived: Base
(3)友元关系与继承
- class Derived: private Base
友元函数或者类指的是在其定义中可以访问类中的成员
但是无法继承,即无法访问派生类中的成员
如:
输出结果为:
- #include "stdafx.h"#include < iostream > using namespace std;
- class Base {
- friend class Frnd;
- private: size_t size;
- public: Base() : size(0) {}
- }
- B;
- class Derived: public Base {
- public: //此处若改为private是无法再class Frnd中调用的
- size_t size_d;
- public: Derived() : size_d(1) {}
- }
- D;
- class Frnd {
- public: size_t size(Base b) {
- return b.size;
- }
- size_t size_d(Derived d) {
- return d.size_d;
- }
- }
- F;
- int _tmain(int argc, _TCHAR * argv[]) {
- cout << F.size(B) << endl;
- cout << F.size_d(D) << endl;
- return 0;
- }
0
1
注意:友元函数或者类一般定义在前面已经声明的类的后面
(4)继承与静态成员
这个好比是静态成员是祖传的秘籍,只有一本,但是谁都可以看,当父辈修改时,子辈再看时,上面就有修改过的痕迹
输出结果为:
- #include "stdafx.h"#include < iostream > using namespace std;
- class Base {
- public: static size_t size;
- }
- B;
- class Derived: public Base {}
- D;
- size_t Derived: :size = 0; //这里的初始化方式,要特别留意,而且可以使Base::size=0
- int _tmain(int argc, _TCHAR * argv[]) {
- cout << D.size++<<D.size << endl;
- return 0;
- }
01
15.3 转换与继承
指的是基类和派生类之间的转化,一般来讲,可以用派生类来对基类进行初始化赋值如:
- Derived D;
- Base B(D); //可以用D来进行初始化
- //当然这些都需要在构造函数中去实现,自己定
- //义 如:
- Base(const Derived & D) {...
- }
但是基类却不能转化为派生类(自动转化),也就是说任何有等号的操作都是错误的如:
- Base * B = D; //error
- Derived D = B; //error
1、派生类构造函数
合成的派生类默认构造函数:
当派生类构造函数中没有对基类中的成员进行初始化时,就会默认调用基类的构造函数
- <pre name="code" class="cpp">
- <pre name="code" class="cpp">
- Derived(int b):p(b){}
向基类构造函数传递实参:
这里注意的一点是,只能间接传递,即向基类构造函数传递实参,不能再初始化列表中直接传递,如下:
注:只能初始化直接基类,直接基类就是在派生列表中指定的类 2、复制控制和继承
- <pre name="code" class="cpp">
- Derived(const string &a,int b):p(b),Base(a){} Base(const string &a):p(a){}
(1)定义派生类复制构造函数
如果派生类定义了自己的复制构造函数,该构造函数还应该显示的使用基类复制构造函数初始化对象的基类部分如:
注:如果没有加 Base(d) 的话,那么会默认调用基类的构造函数,这样就没有将 d 的成员复制给新的构造函数
- Derived(const Derived & d) {
- Base(d);
- a = d.a;...
- }
(2)派生类赋值操作符
同前面一样,也需要在操作符中对基类部分进行显示赋值如:
注:注意这里基类操作符的使用方式。相当于显示调用,而构造函数也可以写成 Base::Base(b);
- Derived & operator = (const B & b) {
- Base: :operator = (b); //注意这里的使用
- return * this;
- }
(3)派生类析构函数
这里和前面两个不一样,只需要析构自己的成员就可以了,会默认调用基类的析构函数
如果不希望调用默认的析构函数,需要自己去析构一些成员,那么可以用虚析构函数,这里就和虚函数式一样的,但是没有虚构造函数和赋值虚函数一说(完全没有必要),虚析构函数的目的,还是动态绑定,利用指针来动态的析构对象如:
- Base * p = new Base;
- delete p;
- p = new Derived;
- delete p;
1、名字冲突与继承
一般来讲,重定义之后,会覆盖基类的函数。可以通过作用域符号来访问基类如:Base::men。
2、作用域与成员函数
(1)如果派生类想重定义基类的所有版本,那么必须定义所有,要么一个都不定义。
但是如果只想在原来的几种版本上重定义几种版本,那么可以使用 using 申明,就跟继承一样,保持某个成员函数不变如:
using Base::Fcn(int a)
(2)只要重定义成员函数,即使函数原型不同,也会覆盖基类的版本如:
(3)虚函数与作用域
- class Base {
- void Fcn() {}
- }
- B;
- class Derived: public Base {
- public: //若不申明是public的话 默认为private
- void Fcn(int a) {}
- }
- D;
- int _tmain(int argc, _TCHAR * argv[]) { < span style = "white-space:pre" > </span>
- <span style="white-space:pre"> </span > //D.Fcn()//error
- < span style = "white-space:pre" > </span>D.Fcn(1);/ / right < span style = "white-space:pre" > </span>return 0;
- }
- /
要求:虚函数不带形参,这样方便动态调用
总结:确定函数调用
a、确定调用函数的对象,指针或引用的静态类型
b、在该类中查找,若找不到,则一步步往上找(继承)
c、找到后进行检查,看调用是否合法
d、若合法,则声称代码,若是虚函数,动态调用,则找是哪个版本
引子:
对于一些类,比如说动物,其派生类为老虎、熊猫等,动物是没有对象的,但是老虎却有,因此将动物作为一个抽象类
在动物类中,声明一个纯虚函数,如脚的个数(不定义,交给派生类去定义)使其变成一个抽象类,使其不能实例化,但是却可以作为派生类的一个组成部分。
如:
输出:
- #include "stdafx.h"#include < iostream > #include < string > using namespace std;
- class Base {
- virtual void Fcn(int a) = 0; //virtual <返回类型><函数名>(形参)=0;
- };
- class Derived: public Base {
- public: void Fcn(int a) {
- HandsNumber = a;
- cout << a << endl;
- } //无法用初始化列表来初始化类成员
- public: int HandsNumber;
- }
- D;
- int _tmain(int argc, _TCHAR * argv[]) {
- Base B; //error
- D.Fcn(1);
- return 0;
- }
1
注:这个在以后肯定会用到的,另外无法用初始化列表来初始化类成员
如:
- vector < Base > basket;
- Base B;
- Derived D;
- basket.insert(B);
- basket.insert(D); //这样的话将它完全转化为基类
来源: http://lib.csdn.net/article/cplusplus/48955