【C++】第二章:CC++类与对象

目录

2.1 类

2.2 成员函数

2.3 常量成员函数

2.4 类与封装

2.5 对象

2.5.1 对象的定义

2.5.2 对象的引用

2.5.3 对象的赋值

2.6 构造函数

2.6.1 无参构造函数(默认构造函数)

2.7 析构函数

2.8 函数的重载

2.9 静态成员

2.9.1 静态成员的声明及定义

2.9.2静态成员的定义:

2.9.3 静态成员的访问

2.10 this指针

2.10.1 this指针的概念

2.11 友元


类与对象是实现数据封装和信息隐藏的工具,是继承和多态的基础。
类可以同时包括数据和函数的定义,对象是由类定义出的变量。
广义上讲:
类与对象就是数据类型与变量的关系,类相当于数据类型,对象就是由数据类型定义的变量。

2.1 类

struct将所有成员都默认为public,类中可以利用访问限定符private私有成员,public共有成员,protected继承有关。
类用class表示,类的声明/定义只是增加了一种自定义的数据类型,不存在为数据成员分配存储空间的事情。
在声明/定义类时对数据成员赋值是错的,此时的数据成员在内存中不存在。
正确的流程是用类定义变量,才会为类的变量分配存储空间,数据成员才会在内存中存在。

2.2 成员函数

在面向对象的程序设计中,类的成员函数叫方法或服务。其定义有两种方法:
一种是声明类时就给出了成员函数的定义。
【例1】

class Data{
    int day,month,year;
    public:
    void init(int d,int m,int y){
        day = d;
        month = m;
        year = y;
    }
}

在Data中,成员函数init()直接在类的內部进行定义,是内联函数。
【例2】
另一种方法是在类声明时只声明成员函数的原型,然后在类的外部定义成员函数。
r_type class_name::f_Name(T1 p1, T2 p2);
其中:
r_type是成员函数的返回类型,class_name是类名,::是域限定符,用于说明f_Name是class_name的成员函数,f_Name是成员函数名。
T1,T2是参数类型;p1,p2是形式参数,在函数原型中无意义可省略。
【例3】Data成员函数也可以用下面的方法定义:

class Date{
    int day, month, year;
    public:
    void init(int , int , int );
    int getDay();
}

int Date::getDay(){return day;}
inline void Date::init(int d,int m,int y){
    day = d;
    month = m;
    year = y;
}

注意:init()是内敛函数,getDay()不是。
在C++中在类外的成员函数前加上关键字inline,该成员函数就被定义为内联函数。

2.3 常量成员函数

在C++中,为了禁止成员函数修改数据成员的值,可以将其设置为常量成员函数,方法就是在函数原型后面加上cinst。

class x{
    ....
    T f(T1, T2, ...)const;
    ....
}

其中T是函数返回类型,f是函数名,T1,T2是各参数的类型,设置为const之后表示该成员函数不会修改任何数据成员的值。
注意:只有类的成员函数才能定义为常量函数,普通的函数不能定义为常量函数。

2.4 类与封装

类具有封装性,可以从两个方面认识封装性:
其一是类能够把数据和算法(操作数据的函数)组合在一起,构成一个不可分割的整体;
其二是类具有隐藏信息的能力,能够有效的把类的内部数据(即私有和受保护的成员)隐藏起来,使外部函数只有通过类的共有成员才能访问的类的内部数据。
封装使类具有内部数据自我隐藏的能力,功能独立的软件模块。用private把不想让其他程序访问的数据和函数设置为私有成员,就可以禁止其他程序对这些数据的随意修改;用public设置一些公有成员,让本类之外的其他函数能够通过这些共有成员,按照类允许的方法访问类的私有数据,实现数据保护的目的。
【设计一个时钟类】

class Clock{
    private:
    int hour, minute, second;
    void run();
    public:
    void setHour(int h) {hour = h;}
    void setMinute(int m) {minute = m;}
    void setSecond(int s) {second = s;}
    void dispTime(){
        cont<<"Now is: "<<hour<<":"<<minute<<":"<<second<<endl;
    }
};

void Clock :: run(){
    while(1)
    {
        if(++second>=60){
            second=0;
            if(++minute == 60){
                minute = 0;
                if(++hour==24)
                    hour=0;
            }
        }
        dispTim();
    }
}

类的公有成员也称为类的接口,外部函数要访问类的内部成员,只有通过类的共有成员才能实现。

2.5 对象

类描述了同类事物共有的属性和行为,类的对象是具有该类所定义的属性和行为的实体。
类是抽象的、概念性的范畴。对象是实际存在的个体。
类与对象的关系实际上就是数据类型与变量的关系,类是一种自定义数据类型,用它定义的变量就是对象。
广义上讲:在面向对象程序设计中任何数据类型定义的变量都可以称之为对象。

2.5.1 对象的定义

类名 对象1,对象2;
用之前的时钟类定义两个对象:
Clock myClock, yourClock;
每个Clock类型的对象都有8个成员,其中3个数据成员hour,minute,second,5个成员函数run(),dispTime(),setHour(),setMinute(),setSecond()。C++会为每个对象独立的分配存储空间,有多少个对象就要分配多少次存储空间。
注意:C++只为每个对象的数据成员分配独立的存储空间,而同一类的成员函数在内存中则只有一份拷贝,供该类的所有对象公用。

2.5.2 对象的引用

对象的引用与结构相似,必须用成员访问限定符“.”作为对象名和对象成员之间的间隔符,形式如下:
对象名.数据成员名
对象名.成员函数名(实参表)
例如访问myClock的成员:
myClock.setHour(12);
myClock.dispTime();

应当注意:在类外只能访问对象的共有成员,不能访问对象的私有和保护成员;如果定义了对象指针,在通过指针访问对象成员时,要用"->"作为指针对象和成员对象之间的间隔符。
【例如】
Clock *pClock
pClock = new Clock;
pClock -> setHour(10);
pClock -> dispTime();

2.5.3 对象的赋值

同一个类的不同对象之间,以及同一个类的对象指针之间可以互相赋值,方法如下:
对象名1 = 对象名2;
例如,对于前面的Clock类,下面的用法是正确的:

Clock *pa, *pb , aClock, bClock;
......
bClock = aClock;
pa = new Clock;
......
pa = ba;

说明:进行赋值的两个对象必须类型相同;对象赋值就是对数据成员拷贝,赋值后,两个对象互不相干,bClock和aClock的数据成员的值是相同的,但赋值完成之后,它们就没有联系了。

2.6 构造函数

在定义类对象时,必须调用适当的构造函数完成对象的创建。构造函数consturctor是与类同名的特殊函数成员,主要用来初始化对象的数据成员。定义如下:
class X{
    ....
    X(...);
    ....
}

其中X是类名,X()就是构造函数,它可以有参数表。构造函数的声明和定义方法与类的其他成员函数相同,可以在类的内部定义构造函数,也可以在类中声明构造函数,然后在类的外部定义构造函数。
X::X(...){
    .....
}

构造函数有以下特点:构造函数与类同名,构造函数没有返回值(void也不行),构造函数可以被重载,构造函数有系统自动调用,不允许在程序中显示调用。
【例如】

#include<iostream>
class point{
    public:
    int x;
    int y;
    void output()
    {
        cout<<x<<endl<<y<<endl;
    }
}
void main()
{
    point pt;
    pt.output();
}

这段代码在执行时输出结果会有两个很大的负数,这是因为在构建pt对象时,系统要为它的成员变量x,y分配内存空间,而在这个内存空间中的值是一个随机值,在程序中我们没有给这两个变量赋值,才会导致这个结果。因此我们要定一个一初始化函数来初始化x,y的坐标。即:

#include<iostream>
class point{
    public:
    int x;
    int y;
    /****新增函数****/
    void init(){
        x=0;
        y=0;
    }    
    /****新增函数****/
    void output()
    {
        cout<<x<<endl<<y<<endl;
    }
}
void main()
{
    point pt;
    pt.output();
}

但是这样不是很方便,能不能在我们定义pt这个对象时,就对pt的成员函数进行初始化呢?在C++中给我们提供了构造函数。
增加构造函数的代码如下:

#include<iostream>
class point{
    public:
    int x;
    int y;
    /****新增构造函数****/
    point(){
        x=0;
        y=0;
    }    
    /****新增构造函数****/
    void output()
    {
        cout<<x<<endl<<y<<endl;
    }
}
void main()
{
    point pt;
    pt.output();
}

构造函数的作用就是对对象本身做初始化,也就是给用户提供初始化类中成员变量的一种方式。当main函数中执行“point pt;”这条语句时,就会自动调用point这个类的构造函数,从而完成对pt对象内部成员函数x和y的初始化工作。
【例如】

#include <iostream>
using namespace std
class Desk{
    public:
    Desk(int, int, int, int);
    void setWeight(int w){weight = w}
    private:
    int weight, length, width, high;
};
Desk::Desk(int ww, int l, int w, int h){
    weight = ww;
    high = l;
    width = w;
    length = h;
    cout<<"call constructor !"<<endl;
}
void main(){
    Desk d1(2,3,3,5);
}

程序为Desk类创建了一个构造函数,它为Desk的4个数据成员提供初值。
构造函数的调用时机是定义对象之后的第一时间,即构造函数是对象的第一个被调用的函数。
对于main()函数中的Desk对象d的定义:
Desk d(2,3,3,5);
编译器可能将其扩展成:
Desk.d;
d.Desk::Desk(2,3,3,5);

编译器首先为对象d分配内存空间,分配完成后就立即自动调用构造函数初始化d对象的各数据成员。

如果一个类中没有定义任何构造函数,那么C++编译器在某些情况会为该类提供一个默认的构造函数,这个默认的构造函数是一个不带参数的构造函数。只要一个类中定义了构造函数,不管这个构造函数是否是带参数的构造函数,C++编译器就不再提供默认的构造函数。

2.6.1 无参构造函数(默认构造函数)

C++规定,每个类必须有构造函数,如果一个类没有构造函数,在需要时,编译器将会为它生成一个默认构造函数:
class X{
    X(){}
    ......
}

默认的构造函数是一个无参数的构造函数,负责对象的创建和初始化。
应当注意:只有在类没有定义任何构造函数时,系统才会产生默认的构造函数。一旦定义了任何形式的构造函数,系统就不再为类生成默认的构造函数。因此,在某些情况下,必须显式定义无参构造函数,以便能够创建无参或数组对象。

2.7 析构函数

当一个对象的生命周期结束时,我们应该释放这个对象所占有的资源,这可以利用析构函数来完成。析构函数的定义格式为:
~类名(),如~point();
析构函数是反向的构造函数。析构函数不允许有返回值,更重要的是析构函数不允许带参数,并且一个类只能有一个析构函数,析构函数用于清除类的对象,当一个类的对象超出它的作用范围,对象所在的内存空间被系统回收,或者在程序中用delate删除对象时,析构函数将自动被调用。对一个对象来说,析构函数是最后一个被调用的成员函数。
根据析构函数的这个特点,我们可以在构造函数中初始化对象的某些成员变量,为其分配内存空间(堆内存),在析构函数中释放对象运行期间所申请的资源。
【例如】

class Student
{
    private:
    char *pName;
    public:
    Student(){
        pName = new char[20];
    }
    ~Student()
    {
        delete[] pName;
    }
};

在Student类的构造函数中,给字符指针变量pName在堆上分配了20个字符的内存空间,在析构函数中调用delete,释放在对上分配的内存。如果没有delete[] pName这句代码,当我们定义一个Student对象,在这个对象生命周期结束时,在它的构造函数中分配的这块内存就会丢失,造成内存泄漏。

2.8 函数的重载

我们希望在构造pt这个对象的同时,传递x和y坐标的值。可以再定义一个构造函数。

#include<iostream>
class point{
    public:
    int x;
    int y;
    /****新增构造函数****/
    point(){
        x=0;
        y=0;
    }    
    /****新增重构函数****/
    point(int a, int b)
    {
        x = a;
        y = b;
    }
    /****新增构造函数****/
    void output()
    {
        cout<<x<<endl<<y<<endl;
    }
}
void main()
{
    point pt(5,5);
    pt.output();
}

当main函数中执行point pt(5,5)时,编译器会根据参数的类型和个数来确定执行哪个构造函数。
重载的构成条件:函数的参数类型,参数个数不同,才能构成函数的重载。
注意:只有函数的返回类型不同是不能构成函数的重载,。+
在函数重载时,要注意函数带有默认参数的情况。

2.9 静态成员

2.9.1 静态成员的声明及定义

在类中,若在数据成员或成员函数的声明或定义前面加上关键字static,就将它定义成了静态数据成员或静态成员函数。
其定义形式如下:

class X{
    ....
    static type dataName;
    static type funName(.....);
    ....
}

静态成员函数是属于类的,整个类只有一份拷贝,相当于类的全局变量,供该类的所有对象公用,能够被该类的所有对象访问;
非静态数据成员是属于对象的,每个对象都有非静态数据成员的一份拷贝,为该对象专用。

2.9.2静态成员的定义:

在类的声明中将数据成员定义为静态成员,只是一种声明,并不会为该函数成员分配内存空间,在使用之前应该对它进行定义。静态数据成员常常在类外进行定义:
类型  类名::静态成员名;
类型  类名::静态成员名 = 初始值;
对于静态成员函数而言,除了在类声明中的成员函数前面加上static关键字之外,其定义与普通函数没有区别。
注意:在类外定义数据成员时,不能加上static限定词;在定义静态数据成员时可以指定它的初始值,若定义时没有指定初始值,系统默认初始值为0。

2.9.3 静态成员的访问

静态成员属于整个类,如果将其定义为共有成员,在类外可用下面两种方式访问。
1)通过类名访问(这种方式非静态成员不具有的)
类名::静态数据成员名;
类名::静态成员函数名(参数表);
2)通过对象访问
对象名.静态成员名;
对象名.静态成员函数名(参数表)
因为静态成员函数属于整个类,因此不论通过哪个对象调用到静态成员函数都是相同的,因此建议通过类调用静态成员函数,以区别普通成员函数的调用。
说明:同普通函数一样,静态成员函数也可以在类的内部或者外部定义,还可以定义成内联函数;静态函数只能访问静态成员,不能访问非静态成员。
例如:

#include <iostream>//标准输入输出流
using namespace std;
class Book{
    private:
    double price;
    static int number
    public:
    double getPrice(){return priceL;}
    static int getNumber(){return number;};
};
Book::Book(char *name, double Price){
    strcpy(bkname, name)
    price = Price;
    number++;
    totalPrice += price
}
int Book::number = 0;//定义并初始化静态数据成员
double Book::totalPrice = 0;

void main(){
    Book b1("《水浒传》",56)
    cout<<b1.getNumber<<endl;
//    cout<<Book.getNumber<<endl;//运行结果同上述一样。
//    cout<<b2.getNumber<<endl;
}
/**********错误示例***********/
int Book::getNumber(){
    price = 20; //getNumber是静态函数,不能访问非静态成员。
    return number;
}

在类外定义静态成员函数时,不能加上static限定词。
/**********错误示例***********/
static int Book::getNumber(){return number;}
静态成员函数可以在定义类的任何对象之前被调用,非静态成员只有在定义对象后,通过对象才能访问。
int x=Book::getNumber();//正确
int y=Book::getPrice();//错误,非静态成员,需要成员函数进行调用

例如:
b1.getPrice();在语法上是正确的。

2.10 this指针

2.10.1 this指针的概念

类的每个对象都有自己的数据成员,有多少个对象就,就有多少份数据成员拷贝。然而类的成员函数只有一份拷贝,不论多少个对象,都共用这份成员函数。那么不同的对象是如何让共用这份成员函数的呢?换句话说,在程序运行时,成员函数如何知道哪个对象在调用它,它应该处理哪个对象的数据成员呢?答案就是指针。
this是用于标识一个对象自引用的隐式指针,代表对象自身的地址,相当于下面的定义:
class X{};
X *this;
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

this指针记录每个对象的内存地址,然后通过运算符->访问该对象的成员。

(1)this只能在成员函数中使用:全局函数、静态函数都不能使用this.

2.11 友元

类的封装具有信息隐藏的能力,他使得外部该函数只能通过类的public成员函数才能访问类的private成员。如果要多次访问类的私有成员,就要多次访问类的公有成员函数,势必会进行频繁的参数传递,参数类型检查和函数调用等操作,不仅麻烦还会占用较多的储存空间和时间,降低程序的循行效率。
能否给某些函数特权,使得它能够直接访问类的私有成员呢?C++给出了友元机制。友元机制允许一个类授权其他函数直接访问类的private和proteded成员。
友元包括友元函数,友元类,友元成员函数,其中友元函数最常用,定义如下:

class X{
    .....
    friend T f(...)
    .....
};
.....

T f(...){.....}//友元函数不是类成员函数,定义时不能用"X::f"限定函数名。
要注意的时,友元编程更简洁,程序运行效率高,但是由于它可以直接访问类的私有成员,破坏了类的封装性和信息隐藏。
另外友元函数并非类的成员函数,将它放在public,private效果都是一样的。
同时友元函数不具有逆向性和传递性,如果A是B的友元,不能代表B是A的友元。如果A是B的友元,B是C的友元,不能代表A是C的友元。
一个类还可以是另一个类的友元:

class A{
    .....
    friend class B;
};
class B{
    .....
};

类B是类A的友元,它的任何成员函数都能直接访问类A的私有成员。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米杰的声音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值