面向对象程序设计(一)

创建新的类型

类的定义

class 类名 {
    private:
        私有数据成员和成员函数
    public:
        公有数据成员和成员函数
};

公有成员(public):类的用户可以调用的信息,是类对外的接口
私有成员(private):只能由类的成员函数调用,被封装在一个类中,类的用户是看不见的。
数据成员一般是私有的;成员函数一般是公有的,成员函数实现时分解出的小函数是私有的。
private 和public的出现次序可以是任意的。也可以反复出现多次。

接口和实现分开

类的定义写在接口文件中,成员函数的实现写在实现文件中。某些简单的成员函数的定义可以直接写在类定义中,称为内联函数

类定义实例

以有理数类为例,应用接口与实现分开。

//接口文件 Rational.h
#ifndef  _rational_h
#define  _rational_h
#include <iostream>
using namespace std;
class Rational {
private:
    int num;
    int den;
    void ReductFraction(); //将有理数化简成最简形式
public:
    void create(int n, int d){num = n; den = d; ReductFraction();}
    void add(const Rational &r1, const Rational &r2); 
    void multi(const Rational &r1, const Rational &r2);
    void display() {cout << num << '/' << den;}
};
#endif 

.

//实现文件
#include "Rational.h"

//add函数将r1和r2相加,结果存于当前对象
void Rational::add(const Rational &r1,  const Rational &r2)
{
    num = r1.num * r2.den + r2.num * r1.den;
    den = r1.den * r2.den;
    ReductFraction();
}

void Rational::multi(const Rational &r1,  const Rational &r2)
{
    num = r1.num * r2.num;
    den = r1.den * r2.den;
    ReductFraction();
}

void Rational::ReductFraction()
{
    int tmp = (num > den) ? den : num;
    for (; tmp > 1; --tmp) 
        if (num % tmp == 0 && den % tmp ==0) 
            {num /= tmp; den /= tmp; break;}
}

对象的使用

类与对象的关系相当于类型与变量的关系

对象的定义

直接在程序中定义某个类的对象:

存储类别 类名  对象列表;

用动态内存申请的方法申请一个动态对象:

Rational  *Rp,*rp;
Rp = new Rational; 
rp = new Rational[20]; 
delete Rp;或 delete []rp;

对象的引用

对象名.数据成员名 或 对象指针->数据成员名
对象名.成员函数名(实际参数表)或 对象指针->成员函数名(实际参数表)
外部函数不能引用对象的私有成员

this指针

每个成员函数都有一个隐藏的指向本类型的指针形参this,它指向当前调用成员函数的对象。通常,在写成员函数时可以省略this,编译时会自动加上它们。如果在成员函数中要把对象作为整体来访问时,必须显式地使用this指针。这种情况常出现在函数中返回一个对调用函数的对象的引用。

对象的构造与析构

构造函数

在定义对象时为对象赋初值;
由系统在定义对象时自动调用;
如果没有给类定义构造函数,编译系统会自动生成一个缺省的构造函数。它只为对象开辟存储空间,空间中的内容为随机数。

构造函数的名字必须与类名相同;
不能说明它的类型;
构造函数可以重载;

初始化列表方法

构造函数初始化列表位于函数头和函数体之间。它以一个冒号开头,接着是一个以逗号分隔的数据成员构造列表。
如DoubleArray的构造函数可写为

DoubleArray::DoubleArray(int lh, int rh):low(lh), high(rh){
    storage = new double [high - low + 1];
}

利用初始化列表可以提高构造函数的效率。在初始化数据成员的同时完成了赋初始的工作。
以下情况必须使用初始化列表:

  • 数据成员不是普通的内置类型,而是某一个类的对象,可能无法直接用赋值语句在构造函数体中为它赋初值
  • 类包含了一个常量的数据成员,常量只能在定义时对它初始化,而不能对它赋值。因此也必须放在初始化列表中。

拷贝构造函数

在创建一个对象时,可以用一个同类的对象对其初始化。这是需要调用一个特殊的构造函数,称为拷贝构造函数。
拷贝构造函数以一个同类对象引用作为参数,它的原型为:

类名 (const <类名> &ob);

如果用户没有定义拷贝构造函数,系统会定义一个缺省的拷贝构造函数。该函数将已存在的对象原式原样地复制给新成员。
一般情况下,默认的拷贝构造函数足以满足要求。但某些情况下可能需要设计自己的拷贝构造函数,比如数据成员涉及到指针,会导致两者指向同一个地,对一者的操作会导致对另一个同样的操作。

拷贝函数应用的场合:

1、对象定义时,将初始值放在圆括号中,直接调用与实参类型相匹配的构造函数。

class point{
    int x, y;
    public: 
        point(int a, int b){x=a; y=b;}
        point(const point &p){x=2*p.x; y=2*p.y;}
        void print() {cout<<x<<"  "<<y<<endl;}
};

void main()
{
    point p1(10, 20), p2(p1), p3 = p1, p4(1, 2); 
    p1.print(); p2.print(); p3.print(); p4.print();
    p4 = p1; p4.print();
}

输出为

10 20
20 40
20 40
1  2
10 20

第三行和第五行不一样,虽然两者都是用等号赋值p1,但是p3是在定义中赋值,使用拷贝函数,p4是单独的一个等号赋值语句。

2、把对象作为值传递参数,如果是引用传递就没有这个构造过程了

3、把对象作为返回值时,当执行到return语句时,会创建一个类的临时对象,并调用拷贝构造函数用返回对象初始化该临时对象,并将此临时对象的值作为返回值。

析构函数

执行与构造函数相反的操作,通常执行一些清理工作,如释放分配给对象的动态空间等。
析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)。
析构函数没有参数,没有返回值,也不能重载。
一般在构造函数中有动态申请内存的,必须有析构函数。

析构顺序:1、局部变量先消失,然后是静态局部变量,最后是全局变量;2、后创建的先消失;

常量对象与const成员函数

const ClassName Obj (params);

const对象不能被赋值,只能初始化,而且一定要初始化,否则无法设置它的值。
数据成员一般都由成员函数修改。当定义了一个const对象后,只能调用const成员函数修改。格式为

type func(params) const {
    ...
}

type ClassName::func(params) const{
    ...
}

常量成员

const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。同一类的不同的对象其const数据成员的值可以不同。

const type variables

不能在类声明中初始化const数据成员。const数据成员的初始化只能在类构造函数的初始化表中进行,不能在构造函数中对他赋值。

const深入

const与引用

是被引用对象的别名;不管被引用对象是常量还是变量,用此名字是不能赋值。const引用的初值可以是常量或表达式。

出现在函数的形式参数前

void func(const type &variable);

表示函数中只能引用不能修改variable的值。variable的实际参数可以是变量、常量或表达式。

出现在函数返回值前,提高函数返回效率

const type &func(params);

函数func的返回值必须是一个离开函数func后依然存在的变量或常量;
函数func只能被引用,不能被赋值。

const与指针

const type *p;: 不能通过p修改它指向的单元的内容,但p可以指向不同的变量。
type *const p = &x;: 可以通过p修改指向的地址的值,但p指的地址不能变,即p永远只能指向x,但x的值可变。P定义时必须给出初值。
const type *const p = &x;: p的值不能修改,*p的值也不能修改。

函数参数。如void f(const int *p)表示采用指针传递,但在函数中不能修改p指向的对象。
函数的返回值。如const int *f()表示函数的返回值是一个指针,该指针指向的对象在离开函数f后依然存在。可以使用const int *p = f();但不能使用int *p = f()

const可以放在成员函数原型后面,表示该成员函数不会修改对象的值,const放在全局函数的后面是没有意义的!!!

静态数据成员及其函数

静态数据成员指整个类所有对象共享的数据,在变量类型前加static

静态数据成员的初始化不能放在类的构造函数中;
静态数据成员不属于对象的一部分,而是类的一部分;
由于静态数据成员属于类,因此定义对象时并不为静态成员分配空间;
但类定义并不分配空间,空间是在定义对象时分配。
上述三句话表明,静态数据成员既不是在定义类也不是在定义类的对象时分配空间。
为静态数据成员分配空间称为静态成员的定义,静态数据成员的定义可以出现在使用类的程序中,或在类的实现文件中。形式为

type ClassName::static_variable = value;

在为static_variable分配空间的同时赋予初值。

静态成员函数
静态的成员函数是为类的全体对象服务,而不是为某个类的特殊对象服务。
由于静态成员函数不需要借助任何对象就可以被调用,所以编译器不会为它暗加一个this指针。因此,静态成员函数无法处理类中的非静态成员变量。
静态成员函数的声明只需要在类定义中的函数原型前加上保留词static。

静态的常量数据成员的声明:
如果成员的类型是整型

static const  int  数据成员名 = 常量表达式; 

如果成员类型是非整型,在类中值是声明,在类外给初值

static const double st; //类声明
const  double  类名::st = 1.5;    //类外

友元

类的私有成员只能通过它的成员函数来访问。友元函数是一扇通往私有成员的后门。
友元的特点:

  • 友元关系是授予的而不是索取的。也就是说,如果函数f要成为类A的友元,类A必须显式声明函数f是他的友元,而不是函数f自称是类A的友元。
  • 友元关系不是对称关系,如果类A声明了类B是它的友元,并不意味着类A也是类B的友元。
  • 友元关系不是传递关系。如果类A是类B的友元,类B是类C的友元,并不意味着类A是类C的友元。

友元声明

friend type func(params);

在类定义中用关键词friend声明友元函数,可声明在public部分,也可声明在private部分。
但一个较好的程序设计的习惯是将所有的友元关系的声明放在最前面的位置,并且不要在它的前面添加任何访问控制说明。

友元函数定义

友元函数是一个全局函数,可以定义在类中,也可以定义在类外,不管定义在哪里,都不是类的一部分。
声明方法:

friend  返回类型   函数名(参数表);

实例

// 定义在类内
class girl{
    char name[10];
    int age;
    public:
        girl(char *n, int d){
            strcpy(name,n);
            age=d;
        }
        friend void disp(girl &x){
            cout<<x.name<<""<<x.age<<endl;
        }
};
void main(){
    girl e("abc", 15);
    disp(e);
}

// 定义在类外
class girl{
    char name[10];
    int age;
    public:
        girl(char *n, int d){
            strcpy(name,n);
            age=d;
        }
        friend void disp(girl &x);
};
void disp(girl &x){
    cout<<x.name<<""<<x.age<<endl;
}
int main(){
    girl e("abc", 15);
    disp(e);
    return 0;
}

友元成员

其他某个类的成员函数;可以访问friend声明语句所在类的私有成员和公有成员的成员函数。
类A的成员函数作为类B的友元函数时,必须先定义类A,再定义类B。在定义类A和B前必须先声明类B。
声明方法:

friend 函数返回类型 类名标识符::函数名(参数列表);  

实例

//girl类的disp函数是boy类的友元
class boy;  
class girl{
    char name[10];
    int age;
    public:
        girl(char *n, int d){strcpy(name,n);age=d;}
        void disp(boy &x);
};
class boy{
    char name[10];
    int age;
    public:
        boy(char *n, int d){strcpy(name,n);age=d;}
        friend void girl::disp(boy &);
};
void girl::disp(boy &x)
{
    cout<<name<<"  "<<age<<endl;
    cout<<x.name<<"   "<<x.age<<endl;
}
int main()
{
    girl e("abc", 15);
    boy b("cde",20);
    e.disp(b);
    return 0;
}

友元类

整个类作为另一个类的友元。当A类被说明为类B的友元时,类A的所有函数都是类B的友元函数。
声明方法:

friend Y;
//或friend class Y;

实例

class boy;
class girl {
    char name[10];
    int age;
    public:
        girl(char *n, int d) {strcpy(name,n);age=d;}
        void disp(boy &x);
};
class boy {
    char name[10];
    int age;
    public:
        boy(char *n, int d) {strcpy(name,n);age=d;}
        friend class girl;
};
void girl::disp(boy &x)
{     
    cout << name << "  “ << age << endl;
    cout << x.name << "   “ << x.age << endl;
}
int main()
{
    girl e("abc", 15);
    boy b("cde",20);
    e.disp(b);
    return 0;
}

注:友元关系可以写在类定义中的任何地方。但一个较好的程序设计的习惯是将所有的友元关系的声明放在最前面的位置,并且不要在他的前面添加任何访问控制说明。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,515评论 1 51
  •   支持面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念。通过实例化类可以...
    伊凡的一天阅读 394评论 0 2
  • 3. 类设计者工具 3.1 拷贝控制 五种函数拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符析构函数拷贝和移...
    王侦阅读 1,800评论 0 1
  • 小肠经 小肠盘绕于腹腔中央,消化的过程多半在这里进行。 小肠接收没有完全分解的食物,继续进行分离和吸收。对身心所有...
    Chaichengxin阅读 145评论 0 0
  • 感觉有很多很多的东西要写出来,可是临到要写,却似乎写不出一个字。嗯……就这样吧!
    影子lonely阅读 84评论 0 0