C++学习——类与对象(基础)

7.1 类和对象的定义

7.1.1 基本概念

  和结构体一样,在程序设计语言中,一个类是一种新的数据类型。
  在c语言中,struct 只能包含成员变量,不能包含成员函数,而在c++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
  c++中的 struct 和 class 基本是通用的,唯有几个细节不同:

  •使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
  •class 继承默认时 private 继承,而 struct 继承默认时 public 继承。
  •class 可以使用模板,而 struct 不能。
  •class 有默认无参构造函数和默认析构函数,而 struct 没有。
  •class 表现为行为,struct常用来存储数据。

7.1.2 类的声明

  类的声明用于具体说明类的组成,一般将类的声明单独用一个扩展名为.h的头文件来保存。
  另外,还需要对所声明的成员函数定义功能。类的声明加上成员函数的定义,就完整的定义了一个类。成员函数定义同常用一个c++源文件来保存。当然,也可以在类声明的同时,实现类的成员函数。
  声明类的语法形式为:

class 类名称
{
    public:
        公有成员
    protected:
        保护型成员 
    private:
        私有成员 
}; 

其中,“成员”既可以是数据成员,也可以是成员函数的原型。关键字public、protected、private 说明类成员的访问控制属性。
  类的成员包括数据成员和函数成员,分别描述问题的属性和操作,是不可分割的两个方面。数据成员的声明方式与一般变量相同;函数成员用于描述类的对象可以进行的操作,一般在类中声明原型,在类声明之后定义函数的具体实现。

  public:类对外的接口,在类声明和类(函数)实现之后,类的对象可以访问‘
  private:只允许本类的成员函数来访问。
  protected:可访问性和 private 相似,其差别在于继承过程中对派生类的影响不同。

  如果不写访问关键字,默认的是 private。

7.1.3 类的实现

  函数的原型声明要在类的主体中,而函数的具体实现一般写再类声明之外。在类声明之后定义成员函数的语法形式为

返回值类型 类名::成员函数名(参数表)
{
    函数体
}

其中,通过“ 类名 ”和作用域操作符“ :: ”来表示函数属于哪个类,其他部分和一般函数的定义相同。
  类的成员函数还可以有多种形态:

  1. 带默认参数值的成员函数

  类的成员函数可以有,默认参数值,其调用规则与普通函数相同,即在参数表中指定值。注意:默认值要写在函数原型生命中,函数实现时不写默认值。

  2.内联成员函数

  两种方式:隐式声明和显式声明。
  隐式声明:在类声明时定义的成员函数都是内联函数。函数定义时没有任何附加说明。
  显式声明:在类声明之后定义内联函数需要在函数头部用关键字 inline 开始,格式如下:
  inline 返回值类型 类名::成员函数名(参数表){函数体}

  3.成员函数的重载

  类名是成员函数名的一部分,所以一个类的成员和另一个类的成员函数即使同名,也不能认为是重载。
  例如,可以在 Clock 类中再声明一个按另一种格式显示时间的ShowTime(int n)函数,和用来显示时间的函数构成重载:

class Clock
{
    public:
        void ShowTime();
        void ShowTime(int n);
        ......//        其他成员声明 
};

7.1.4 对象的定义和使用

  定义一个对象和定义一个一般变量相同,语法形式为
    类名称 对象名称;
  类的成员时抽象的,对象的成员才是具体的。类声明中的数据成员一定不能有具体的属性值。
  声明了类及其对象,在类的外部就可以访问对象的公有成员了。
  数据成员:
    对象名.公有数据成员
  函数成员:
    对象名.公有成员函数名(参数表)
  在类的外部,只能通过对象访问类的公有成员;在类的成员函数内部,可以直接访问类的所有成员,这就实现了对访问范围的有效控制。

7.1.5 类的作用域与可见性

  1. 类的作用于域

  一个类的所有成员位于这个类的作用域内,一个类的任何成员函数都能访问同一类的任何其他成员。
  类作用域是指类定义和相应的成员函数定义的范围,通俗地成为类的内部。在该范围内,一个类的成员函数对本类的其他成员具有无限制的访问权。在类的作用域外,对一个类的数据成员或函数成员的访问受到程序员的控制。

  2. 类的可见性

  类名实际上是个类型名,允许类与其他类型变量或其他函数同名。
  在类的内部,与类或类的成员同名的全局变量名函数名不可见。
  在一个函数内,同名的类和变量可以同时使用,都是可见的。例如,若 Clock 类已定义,以下函数的定义是没有问题的。

void func()
{
    class Clock a;            //定义时要用到类名
        int Clock = 10;       //变量名和类型名相同 
        Clock++;
}

7.2 构造函数

  1. 构造函数的定义

  构造函数用来完成对象的初始化,给对象的数据成员赋初值。
  定义构造函数的一般形式为

class 类名
{
    public:
        类名(形参表);    //构造函数的原型
        //类的其他成员 
};
类名::类名(形参表)      //构造函数的实现
{
    //函数体 
} 

  构造函数可以在类的内部实现,也可以在类的外部实现。
  构造函数声明并且实现后,就可以在main()函数中,通过
Clock 类的构造函数来创建和初始化对象。
    类名称 对象名称(参数表);
  构造函数的特点是:构造函数的名称与类名相同,构造函数没有返回值,构造函数一定是公有函数。
  作为类的成员函数,构造函数可以直接访问类的所有数据成员。
  在类的内部定义的构造函数是内联函数。构造函数可以带默认形参值,也可以重载。

  2. 构造函数的重载

  构造函数可以像普通函数一样重载,调用时根据参数的不同,选择其中合适的一个。

  3. 带默认参数值的构造函数

  函数可以为其参数设置默认值,构造函数也可以。

  4. 默认构造函数和无参构造函数

  如果再定义类时没有定义构造函数,则系统在编译时自动生成一个默认形式的构造函数。默认构造函数具有以下形式:
    类名::类名( ){ }
这是一个既没有形式参数,也没有任何语句的函数,这样的默认构造函数当然不能为对象初始化做任何事情。
  注意:只有在类中没有定义任何构造函数的情况下,才能使用默认构造函数。
  还有一种构造函数成为无参构造函数,它的一般形式是:
    类名::类名( ){语句···}
  另外,带有全部默认参数值的构造函数也是无参构造函数。
  假如 Clock 类中只定义了构造函数 Clock(int H,int M,int S),并且没有默认值,以下程序就会有语法错误。

void main()
{
    Clock c1(10,10,10);
    Clock c2;           //编译时会出错 
}

  原因是类中没有定义无参构造函数,因为类中定义了带参数的构造函数,编译系统也就不会再自动生成一个默认构造函数。
  程序中,不能同时出现无参数构造函数和带有全部默认值形参值的构造函数,否则,就会出现编译错误。
  注意,一旦定义了一个类的构造函数,系统就不再生成默认构造函数了。如果需要定义一个对象而不提供实际参数,需要定义一个无参构造函数,或者给所有参数都设置默认值。

  5. 复制构造函数

  复制构造函数用来复制一个对象。定义对象时,通过等号赋值进行对象的初始化,系统会自动调用复制构造函数。例如:

Clock c1(10,10,10);   //先创建一个对象
Clock c2 = c1;        //在定义c2时用c1初始化,自动调用复制构造函数

  也可以在定义对象时,像调用构造函数一样,调用复制构造函数,只是实参时一个已经定义好的对象,例如:

Clock c2(c1);    //利用复制构造函数将c1复制到c2

  复制构造函数就是函数的形参是类的对象的引用的构造函数。
  定义一个复制构造函数的一般形式为

class 类名
{
    public:
        类名(类名& 对象名);   //复制构造函数原型   
};
类名::类名(类名& 对象名)  //复制构造函数的实现
{
    //函数体   
} 

  复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类对象的引用,其作用是使用一个已经存在的对象(由复制构造函数的参数指定的对象)去初始化一个新的同类的对象。复制构造函数与原来的构造函数实现了函数的重载,如果程序在类定义时没有显示定义复制构造函数,系统也会自动生成一个默认的复制构造函数,将成员一一复制。
  但是,某些情况下必须显示定义一个复制构造函数。例如:当类的数据成员包括指针变量时,类的构造函数用 new 运算符为这个指针动态申请空间,如果复制时只是简单地一一复制,就会出现两个对象指向相同的堆地址,则在退出运行时,程序会报错。这种情况必须定义复制构造函数,在复制构造函数中为新对象申请新的堆空间。

7.3 析构函数

  对象所占用的空间要通过析构函数来释放。析构函数的原型是:
    ~类名( );
  如果程序中不定义析构函数,系统也会提供一个默认的析构函数:
    ~类名( ){ };
这个析构函数只能用来释放对象的数据成员所占用的空间,但不包括堆内存空间
  特点:

  •访问属性:公有成员函数。
  •函数名:在类名前加“~”构成。
  •不能重载无参数,无返回值。
  •自动生成:析构函数是在对象生存期即将结束的时刻由系统自动调用的。如果没有定义析构函数,系统将自动生成和调用一个默认析构函数。

例7-4 定义学生类 student,数据成员包括学号、姓名、年龄、成绩;成员函数有构造函数、析构函数和输出显示函数。其中,“姓名”用字符指针(char*)来保存,在构造一个学生时,从堆中为“姓名”分配存储空间,那么需要定义析构函数,在对象生存期结束时,把堆空间释放,归还给系统。在这种情况下,也需要定义一个复制构造函数。

#include<iostream>
using namespace std;
class student
{
    public:
        student(int,char*,int,float);
        student(student&);          //复制构造函数 
        ~student();
        void printstu();
    private:
        int id;
        char* name;
        int age;
        float score;
};
student::student(int i,char* c,int a,float s)
{
    cout << "Constructing..." << endl;
    id = i;
    age = a;
    score = s;
    name = new char[strlen(c)+1];
    if(name != 0)
        strcpy(name,c);
}
student::student(student& s)      //复制构造函数 
{
    cout << "Copy Constructing..." << endl;
    id = s.id;                      //一般成员的简单复制
    age = s.age;
    score = s.score;
    name = new char[strlen(s.name)+1];     //先申请堆空间
    if(name != 0)
        strcpy(name,s.name);        //复制字符串
}
student::~student()
{
    cout << "Destructing..." << endl;
    delete []name;
    name = 0;
}
void student::printstu()
{
    cout << "学号:" << id << "姓名:" << name;
    cout << "  年龄:" << age << "成绩:" << score << endl; 
}
void main()
{
    student stu(1,"wang",18,86);
    stu.printstu();
}

  类的析构函数不能重载,因为析构函数没有参数,因而无法重载,即构造对象的方式有许多种,但释放对象只有一种方式。

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