新标准C++程序设计读书笔记_类和对象

面向对象的程序设计方法

抽象:将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性);将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构。

封装:通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“ 类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系。

从客观事物抽象出类

写一个程序,输入矩形的长和宽,输出面积和周长。

比如对于“矩形”这种东西,要用一个类来表示,该如何做“抽象”呢?
矩形的属性就是长和宽。因此需要两个变量,分别代表长和宽。

一个矩形,可以有哪些行为呢(或可以对矩形进行哪些操作)?
矩形可以有设置长和宽,算面积,和算周长这三种行为(当然也可以有其他行为)。这三种行为,可以各用一个函数来实现,他们都需要用到长和宽这两个变量。

将长、宽变量和设置长,宽,求面积,以及求周长的三个函数“封装”在一起,就能形成一个“矩形类”。
长、宽变量成为该“矩形类”的“成员变量” ,三个函数成为该类的“成员函数” 。 成员变量和成员函数统称为类的成员。

下面就是一个最简单的类

 1 class CRectangle
 2 {
 3 public:
 4     int w, h;
 5
 6     int Area()
 7     {
 8         return w * h;
 9     }
10
11     int Perimeter()
12     {
13         return 2 * ( w + h);
14     }
15
16     void Init( int w_,int h_ )
17     {
18         w = w_; h = h_;
19     }
20 }; 

类成员的可访问范围

在类的成员函数内部,能够访问:
(1)当前对象的全部属性、 函数;
(2)同类其它对象的全部属性、 函数。

在类的成员函数以外的地方,只能够访问该类对象的公有成员。

 1 class CEmployee
 2 {
 3 private:
 4     char szName[30]; //名字
 5 public :
 6     int salary; //工资
 7     void setName(char * name);
 8     void getName(char * name);
 9     void averageSalary(CEmployee e1,CEmployee e2);
10 };
11
12 void CEmployee::setName( char * name)
13 {
14     strcpy( szName, name); //ok
15 }
16
17 void CEmployee::getName( char * name)
18 {
19     strcpy( name,szName); //ok
20 }
21
22 void CEmployee::averageSalary(CEmployee e1, CEmployee e2)
23 {
24     cout << e1.szName; //ok,访问同类其他对象私有成员
25     salary = (e1.salary + e2.salary ) / 2;
26 }
27
28 int main()
29 {
30     CEmployee e;
31
32     strcpy(e.szName,"Tom1234567889"); //编译错,不能访问私有成员
33     e.setName( "Tom"); // ok
34     e.salary = 5000; //ok
35
36     return 0;
37 }

构造函数

成员函数的一种,名字与类名相同,可以有参数,不能有返回值(void也不行),作用是对对象进行初始化,如给成员变量赋初值。

(1)如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数,默认构造函数无参数,不做任何操作
(2)如果定义了构造函数,则编译器不生成默认的无参数的构造函数
(3)对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
(4)一个类可以有多个构造函数
(5)构造函数最好是public的, private构造函数不能直接用来初始化对象

下面看2个例子:

 1 class Complex
 2 {
 3 private :
 4     double real, imag;
 5 public:
 6     void Set( double r, double i);
 7 }; //编译器自动生成默认构造函数
 8
 9 Complex c1; //默认构造函数被调用
10 Complex * pc = new Complex; //默认构造函数被调用
 1 class Complex
 2 {
 3 private :
 4     double real, imag;
 5 public:
 6     Complex( double r, double i = 0);
 7 };
 8
 9 Complex::Complex( double r, double i)
10 {
11     real = r; imag = i;
12 }
13
14 Complex c1; // error, 缺少构造函数的参数
15 Complex * pc = new Complex; // error, 没有参数
16 Complex c1(2); // OK
17 Complex c1(2,4), c2(3,5);
18 Complex * pc = new Complex(3,4);

 构造函数在数组中的使用

 1 class Test
 2 {
 3 public:
 4     Test( int n) { } //(1)
 5     Test( int n, int m) { } //(2)
 6     Test() { } //(3)
 7 };
 8
 9 // 三个元素分别用(1),(2),(3)初始化
10 Test array1[3] = { 1, Test(1,2) };
11 // 三个元素分别用(2),(2),(1)初始化
12 Test array2[3] = { Test(2,3), Test(1,2) , 1};
13 //两个元素分别用(1),(2) 初始化
14 Test * pArray[3] = { new Test(4), new Test(1,2) };

复制构造函数

只有一个参数,即对同类对象的引用。形如 X::X( X& )或X::X(const X &), 二者选一,后者能以常量对象作为参数

(1)如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。

1 class Complex
2 {
3 private :
4     double real,imag;
5 };
6
7 Complex c1; //调用缺省无参构造函数
8 Complex c2(c1);//调用缺省的复制构造函数,将 c2 初始化成和c1一样

(2)如果定义的自己的复制构造函数,则默认的复制构造函数不存在。

 1 class Complex
 2 {
 3 public :
 4     double real,imag;
 5
 6     Complex(){ }
 7     Complex( const Complex & c )
 8     {
 9         real = c.real;
10         imag = c.imag;
11         cout << “Copy Constructor called”;
12     }
13 };
14
15 Complex c1;
16 Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called

(3)当用一个对象去初始化同类的另一个对象时会调用复制构造函数

1 Complex c2 = c1; //初始化语句,非赋值语句
2
3 CMyclass c1,c2;
4 c2 = c1; //赋值语句

对象间赋值并不导致复制构造函数被调用

(4)如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用

(5)如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用

类型转换构造函数

定义转换构造函数的目的是实现类型的自动转换。
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。

 1 class Complex {
 2 public:
 3     double real, imag;
 4
 5     //类型转换构造函数
 6     Complex( int i)
 7     {
 8         cout << "IntConstructor called" << endl;
 9         real = i; imag = 0;
10     }
11
12     Complex(double r,double i) {real = r; imag = i; }
13 };
14
15 int main ()
16 {
17     Complex c1(7,8);
18     Complex c2 = 12;
19     c1 = 9; // 9被自动转换成一个临时Complex对象
20     cout << c1.real << "," << c1.imag << endl;
21     return 0;
22 }

析构函数

(1)名字与类名相同,在前面加‘ ~’ , 没有参数和返回值,一个类最多只能有一个析构函数。
(2)析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
(3)如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
(4)如果定义了析构函数,则编译器不生成缺省析构函数。
(5)析构函数在对象作为函数返回值返回后被调用。

下面看个析构函数的例子:

 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 class CMyclass
 6 {
 7 public:
 8     CMyclass() {}
 9     CMyclass(const CMyclass & sobj) { cout << "copy constructor" << endl; }
10     ~CMyclass() { cout << "destructor" << endl; }
11 };
12
13 CMyclass obj;
14
15 //参数对象消亡也会导致析构函数被调用
16 CMyclass fun(CMyclass sobj )
17 {
18     cout << "here_2" << endl;
19
20     return sobj; //函数调用返回时生成临时对象返回
21 }
22
23 int main()
24 {
25     cout << "here_1" << endl;
26
27     //函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用
28     obj = fun(obj);
29
30     cout << "here_3" << endl;
31
32     return 0;
33 }

运行结果:

here_1:main函数最开始

copy constructor:调用函数fun,传入的参数是一个对象

here_2:进入了函数fun中

copy constructor:返回的是一个对象

destructor:退出函数fun,参数被析构

destructor:函数fun的返回值是个临时对象,被使用后,也被析构

here_3:没什么好说的

destructor:析构全局的obj对象

再来看一个综合的例子:

 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 class Demo {
 6     int id;
 7 public:
 8     Demo(int i)
 9     {
10         id = i;
11         cout << "id=" << id << " constructed" << endl;
12     }
13     ~Demo()
14     {
15         cout << "id=" << id << " destructed" << endl;
16     }
17 };
18
19 Demo d1(1);
20
21 void Func()
22 {
23     static Demo d2(2);
24     Demo d3(3);
25     cout << "func" << endl;
26 }
27
28 int main ()
29 {
30     Demo d4(4);
31     d4 = 6;
32     cout << "main" << endl;
33     {
34         Demo d5(5);
35     }
36     Func();
37     Func();
38     cout << "main ends" << endl;
39     return 0;
40 }

运行结果:

就解释一下id=6 constructed和id=6 destructed,这是因为调用了类型转换构造函数,生成了临时的对象

this指针

其作用就是指向成员函数所作用的对象

(1)非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。
(2)静态成员函数中不能使用 this 指针!因为静态成员函数并不具体作用与某个对象!因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数!

 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 class A
 6 {
 7     int i;
 8 public:
 9     //void Hello(A * this ) { cout << "hello" << endl; }
10     void Hello() { cout << "hello" << endl; }
11 };
12
13 int main()
14 {
15     A * p = NULL;
16
17     //Hello(p);
18     p->Hello();
19
20     return 0;
21 } 

运行结果:

 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 class A
 6 {
 7     int i;
 8 public:
 9     //void Hello(A * this ) { cout << this->i << "hello" << endl; }
10     void Hello() { cout << i << "hello" << endl; }
11 };
12
13 int main()
14 {
15     A * p = NULL;
16
17     //Hello(p);
18     p->Hello();
19
20     return 0;
21 } 

运行结果:

静态成员变量

静态成员:在定义前面加了static关键字的成员。

(1)普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
(2)普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
(3)sizeof 运算符不会计算静态成员变量。
(4)基于(1)和(2)的缘故,静态成员不需要通过对象就能访问。
(5)静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。静态成员函数本质上是全局函数。
(6)设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
(7)必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过。

 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 class CRectangle
 6 {
 7 private:
 8     int w, h;
 9     static int nTotalArea;
10     static int nTotalNumber;
11 public:
12     CRectangle(int w_,int h_);
13     ~CRectangle();
14     static void PrintTotal();
15 };
16
17 CRectangle::CRectangle(int w_,int h_)
18 {
19     w = w_;
20     h = h_;
21     nTotalNumber ++;
22     nTotalArea += w * h;
23 }
24 CRectangle::~CRectangle()
25 {
26     nTotalNumber --;
27     nTotalArea -= w * h;
28 }
29 void CRectangle::PrintTotal()
30 {
31     cout << nTotalNumber << "," << nTotalArea << endl;
32 }
33
34
35 int main()
36 {
37     CRectangle r1(3,3), r2(2,2);
38     //cout << CRectangle::nTotalNumber; // Wrong , 私有
39     CRectangle::PrintTotal();
40     r1.PrintTotal();
41     return 0;
42 }

编译结果:

需要改为如下:

1 int CRectangle::nTotalNumber = 0;
2 int CRectangle::nTotalArea = 0;

即使这样编译通过了,上面的例子还有有缺陷的。

在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象(调用一个以CRectangle类对象作为参数的函数时或者调用一个以CRectangle类对象作为返回值的函数时);临时对象在消亡时会调用析构函数,减少nTotalNumber 和nTotalArea的值,可是这些临时对象在生成时却没有增加nTotalNumber 和 nTotalArea的值

这时我们需要再自己写一个复制构造函数

1 CRectangle::CRectangle(CRectangle & r )
2 {
3     w = r.w; h = r.h;
4     nTotalNumber ++;
5     nTotalArea += w * h;
6 }

(8)在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。

时间: 2024-11-07 16:14:20

新标准C++程序设计读书笔记_类和对象的相关文章

新标准C++程序设计读书笔记_运算符重载

形式 返回值类型 operator 运算符(形参表) { …… } 运算符重载 (1)运算符重载的实质是函数重载(2)可以重载为普通函数,也可以重载为成员函数 1 class Complex 2 { 3 public: 4 double real,imag; 5 Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { } 6 Complex operator-(const Complex & c); 7 }; 8 9 Complex

新标准C++程序设计读书笔记_继承和多态

简单继承的例子: 1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 class CStudent 6 { 7 private: 8 string name; 9 string id; //学号 10 char gender; //性别,'F'代表女, 'M'代表男 11 int age; 12 public: 13 void PrintInfo(); 14 void SetInfo( const

Objective-C学习笔记_类和对象

一 Objective-C概述 二 面向对象编程OOP Object Oriented Programming 三 类和对象 OC中类的定义 接口部分 实现部分 类和文件 创建对象 使对象 四 实例变量操作 一 Objective-C概述 Cocoa和Objective-C是苹果公司Mac OS X操作系统的核心.1980年初,Brad Cox发明了Objective-C,意将流行的.可移植的C语言和Smalltalk语言结合在一起:1985年,Steve Jobs成立NeXT公司,NeXT选择

C++旧源码向新标准移植陷井(一)_局部栈变量的生命周期

之前在VC++6.0上面写了下面这样的代码: int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; //不重要的部分略过if (argc>1) {if (strcmp(argv[1],"createpcsstep") == 0) { CDlgCreateStepPcs dlg; dlg.DoModal(); break; } else if (strcmp(argv[1],"clea

汇编语言程序设计读书笔记(3)- 程序范例

主要描述三方面的内容:第一是汇编语言的程序模版,以及模版涉及到的一些知识点:第二是如何调试汇编语言:第三是如何在汇编语言中调用C库函数. 1. 汇编语言的组成 汇编语言由段(section)组成,一个程序中执行的代码,叫文本段(text),程序还可能有定义变量,有付给初始值的变量放在数据段(data)中,没有赋初值或者付给零初值的放在bss段中.text段一定是要有的,data和bss可以没有. 2. 段的定义 用.section语法定义段.比如: .section .text定义文本段, .s

多态实现的原理------新标准c++程序设计

"多态"的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定.例子: #include<iostream> using namespace std; class A{ public: int i; virtual void func(){}; virtual void func2(){}; //如果为只有一个去掉 virtual 关键字即virtual void func2(){};变为 void func2(){}; 输

《循序渐进DB2.DBA系统管理、运维与应用案例》(牛新庄著)读书笔记2

<循序渐进DB2.DBA系统管理.运维与应用案例>(牛新庄著)读书笔记2                                              Windows下的DB2向导安装 现在的DB2已经支持了广泛的IT基础设施,可以在众多平台上运行.Windows.Linux.UNIX这三种平台统称为luw平台.对于自学DB2的人而言,由于不可能自己购买大型机或高端的服务器,所以更多的是把DB2装在luw平台上进行练习.而如果想要尽可能的贴近真实的生产环境,为日后工作打基础,最好是

Effective Java读书笔记(3对于所有对象都通用的方法)

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

Objective-C学习笔记_类的扩展

一Category的定义和使用 二Extension的定义和使用 三Protocol的定义和使用 delegate的使用 一.Category的定义和使用 Category,分类或类目.主要作用是为没有源代码的类添加方法.通过Category添加的方法会成为原类的一部分.从而达到扩展一个类的功能. 定义Category过程 新建?件 选择Objective-C Category模板 填写类名和分类名 .h?件添加?法声明 .m添加?法实现 Category的声明 NSString+SayHi.h