C++ 类与对象(一)

一、什么是类和对象

简单来说,类是数据和对数据的操作(成员函数/类方法)的组合,而对象是类的实例。

比如人是一个类,小王、小张(具体的某个人)就是这个类的对象。

人的年龄、性别是数据。

对年龄的修改(又长大了一岁age++)就是对数据的操作。

通常,把数据称作类成员,而把函数称作类方法或类成员函数。

二、如何定义一个类

关键字:class

以“人”这一类为例

每个人都有自己的名字,年龄,性别等。

而类中有对这些数据的操作,如设置名字、增加岁数等。

定义如下:

class Person
{
private://这是访问权限,待会会介绍
    //数据
    string m_name;
    int m_age;
    string m_gender;

public://访问权限
    //对数据的操作
    void set_name(string name)
    {
         m_name=name;
    }
    
    void add_name()
    {
        m_name++;
    }
}       

可以看到,成员前加了m_,这样有利于区分成员内的变量还是成员外的变量

例如,如果用name作成员变量,则在成员函数set_name(string name)中我们无法知道到底是哪个name。

set_name(string name)
{
    name=name;
}

当然,定义一个类时,可以不给出函数的实现,而是后来再给出其实现。此时,要访问类成员函数,则需要作用域解析运算符::,在函数名前加上“类名::”

void Person::set_name(string name)
{
    m_name=name;
}

三、类的实例化——对象

如果要创建一个名为“果冻”的人,代码如下

int main()
{
    Person Guodong;
}

可以看到,之前已经创建了名为Person的类,如果要实例化,则可以类比  int a;

即“类的名称+实例(对象)的名称”。

四、如何访问类成员以及访问权限

简单来说,就是用成员运算符访问类成员,就像struct一样。

如,要访问成员函数set_name,代码如下:

int main()
{
    Person Guodong;
    Guodong.set_name(Xuegao);
}

在上述(第二点)例子中,可以看到,相比C语言中的struct,class不仅多了函数,还多了private和public。这就是类的访问权限。

类有三种访问权限:public(公有),private(私有),protected(保护)

public权限,指类内和类外都可以访问到

private和protected权限,指只有类内和类外才能访问到

这里着重介绍前两个。

int main()
{
    Person GuoDong;
    GuoDong.m_name=Xuegao;//错误!访问权限为private
    GuoDong.set_name(Xuegao);//正确!访问权限为public
}

在以上例子中,实例化了一个对象“果冻”,而m_name为私有,setname函数为公有

故在main函数中,不能直接访问m_name,而是要通过set_name函数来访问name。

实际上,将成员属性私有化(private),是C++中很重要的思想。

不仅可以自己控制读写权限,还能检测数据的有效性。

首先看第一个好处——可以自己控制读写权限。

class Person
{
private:
    string m_name;//可读可写
    int m_age;//可读不可写
    string m_gender;//可写不可读

public:
    //名字的写
    set_name(string name)
    {
         m_name=name;
    }
    //名字的读
    get_name()
    {
    return m_name;
    }
    
    //年龄的读
    get_age()
    {
    return m_age;
    }

    //性别的写
    set_gender(gender)
    {
    m_gender=gender;
}       

在以上代码中,可以通过set_name和get_name实现对name的读和写,age和gender也是类似。

再来看第二个好处——可以检测数据的有效性。

将成员函数set_age改为如以下函数:

set_age(int age)
{
    if(age>200)
    {
        cout<<"年龄太大,你确定吗?"<<endl;
        return;
    }
    m_age=age;
}

如果输入的年龄超过了200,则不会将数据修改,并且会输出提示消息。这样就检测了数据的有效性。

五、不同对象共用一个类方法

每个对象都有自己的存储空间,用于存储其内部变量和类成员;但同一个类的所有对象共享一组方法(函数)。

例如,声明了Person类的两个对象果冻和雪糕,并分别调用add_name函数

int main()
{
    Person Guodong;
    Person Xuegao;
    Guodong.add_name();
    Xuegao.add_name();
}

此时,Guodong.add_name( )和Xuegao.add_name( )执行的是同一块代码,只是将这些代码用于不同的数据(果冻将add_name函数用于自己的m_name,雪糕也是如此)

六、关于类的成员函数:


定义位于类声明中的函数都将自动成为内联函数,因此add_name是一个内联函数。

class Person()
{
private:
    int m_age;

public:
    void set_name(string name)
    {
        m_name=name;
    }

    void add_name()  
    {
        m_age++;
    }
}

也可以在类声明之外定义成员函数,并使之成为内联函数。只需使用incline即可,如以下代码:

class Person
{
private:
    int m_age;

public:
    void set_name(string name)
    {
        m_name=name;
    }

    void add_name();
}

incline void Person::add_name()  
{
    m_age++;
}

七、构造函数和析构函数

1、构造函数:主要是在创建对象时为对象的成员属性附初值,在创建对象时由编译器自动调用。

2、析构函数:主要用于执行一些清理工作,在对象销毁前由编译器自动调用。如构造函数中用了new,则析构函数中使用delete来释放内存。

先来看最普通的构造函数和析构函数

语法:

构造函数:类名(){ ... }

析构函数:~类名(){ ... }

例子:

class Person
{
private:
    string m_name;
    int m_age;
    string m_gender;

    Person()
    {
        cout<<"Person的构造函数的调用"<<endl;
        m_age=0;
    }

    ~Person()
    {
        cout<<"Person的析构函数的调用"<<endl;
    }

public:
    set_name(string name)
    {
         m_name=name;
    }
}      

接下来看看稍微复杂一点的构造函数。

构造函数,按照有无参数,可以分为有参构造函数和无参构造函数,还可以分为普通构造函数和拷贝构造函数。

有参构造函数可以用于类对象的初始化。

class Person
{
    //无参构造函数
    Person()   
    {
        ...
    }

    //有参构造函数
    Person(int a)
    {
        ...
    }
    
    //拷贝构造函数
    Person(const Person& p)
    {
        m_age=p.age;
        ...    
    }
}

在创建一个对象时,会调用不同的构造函数,并且有多种方法创建一个对象。

1、括号法

int main()
{
    Person p1;//调用默认(无参)构造函数
    Person p2(10);//调用有残构造函数
    Person p3(p2);//调用拷贝构造函数
}

注:为什么调用默认构造函数的时候,不用括号呢?

因为如果有以下代码:

Person p1();

编译器会认为这是一个无参函数,p1为函数名,Person为函数返回类型。

2、显示法

int main()
{
    Person p1;//默认构造
    Person p2=Person(10);//有参构造
    Person p3=Person(p2);//拷贝构造
}

其中,Person(10)为一个匿名对象。

3、隐式转换法

int main()
{
    Person p1=10;//有参构造
    Person p2=p1;//拷贝构造
}

注:无法使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。

八、拷贝构造函数调用时机

1、使用一个已经创建完毕的对象来初始化一个新对象

2、值传递的方式给函数参数传值

3、以值方式返回局部对象

九、构造函数的调用规则

默认情况下,C++编译器至少给一个类添加三个函数:

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行拷贝

规则:

1、若用户定义了有参构造函数,C++不再提供默认构造函数,但是会提供拷贝构造。

2、若用户定义了拷贝构造函数,C++不再提供其他构造函数。

所以若定义了构造函数后,就必须为类提供默认构造函数。且用户定义的默认构造函数通常给所有成员提供隐式初始值。

十、静态成员

1.静态成员变量

特点:(1)所有对象共享一份数据 (2)在编译阶段分配内存 (3)类内声明,类外初始化

class Person
{
//类内声明
public:
    static int m_A;
private:
    static int m_B;
};

//类外初始化
int Person::m_A=10;
int Person::m_B=10;


//两种访问方式
void test()
{

//1.用对象访问
    Person p1;
    p1.m_A=100;

//2.用类名访问
    cout<<Person::m_A<<endl;

2.静态成员函数

特点:(1)所有对象共享一个函数   (2)静态成员函数只能访问静态成员变量

class Person
{
public:
    static int m_A;
    int m_B;
    static void func()
    {
        m_A=100;
        m_B=100;//错误!m_B不是静态成员变量
    }
}

int Person::m_A=100;

//两种访问方式
void test()
{
    //1.通过对象访问
    Person p1;
    P1.func();

    //2.通过类名访问
    Person::func();
}

十一、深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作。

深拷贝:在堆区重新申请空间,进行拷贝操作。

class Person{
public:
    Person(){}
    Person(int age,int height){
        m_age=age;
        m_height=new int(height);
    }
    Person(const Person& p){
        m_age=p.m_age;
        m_height=new int(*p.m_height);
    }
    ~Person(){
        if(m_height!=NULL){
            delete m_height;
        }
    }

public:
    int m_age;
    int* m_height;
}

参考资料:

1、bilibili 黑马程序员 “黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难”

2、《C++ primer plus 第6版》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

云霄星乖乖的果冻

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

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

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

打赏作者

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

抵扣说明:

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

余额充值