C++类和对象

1.类的定义

1.1类的格式

(1)class为定义的类关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面要跟分号。

(2)为了区分类中的成员变量,一般我们命名的时候会在前面加上_或m开头,这个不是硬性要求。

(3)C++中也兼容struct的用法,struct升级成了类,但他仍兼容C语言的用法,同时也可以在struct中定义函数,但我们一般还是用class来定义函数。

(4)定义在类里面的成员默认是inline。

根据上图我们不难看出,通过class和struct都可以定义出类,但是我们用类实例化出对象后,为什么d1中的Init不可访问呢?这时候我们就要引出下一个概念访问限定符。

1.2访问限定符

1.2.1访问限定符的作用

C++是一种实现封装的方式,利用类将对象和方法结合到一块,使对象更加完善,通过访问限定符选择性的将接口提供给外部用户使用。

1.2.2访问限定符的分类

(1)public:公有

(2)private:保护

(3)protected:私有

私有和保护指的是只能在类域的内部进行访问,出了类域就无法被访问,而公有无论是类域里还是类域外都可以被访问。那么私有和保护有什么区别呢?这里我们先不做区分,在今后的章节我们在着重讲解,目前先把他们两个理解为一个意思。

1.1.3访问限定符的使用

(1)public修饰的对象或方法可以直接被外界访问;protected和private修饰的成员不能直接被访问。

(2)访问权限作用域从访问限定符出现的位置开始,直到下一个访问限定符类出现,若没有则直接到类的作用域结束。

(3)class定义默认为private,struct默认为public。

有了上面指示的铺垫我们就可以把1.1中出错的代码改为:

class Data1
{
public:
    void Init(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

这样就可以访问Data1中的函数了,需要注意的是我们一般把类的方法设置为公有,把类的成员对象设置为私有或保护。

1.3类域

前面我们提到了C++中有全局域、局部域、命名空间域、类域,今天我们就来说一说类域。

类域是一个新的作用域,需要用实力化的类的对象来访问类域中的方法,对于分文件写类域的方法,我们一边在类域中写声明,但在实现方法的文件中实现方法时要在函数名处指定类域。

2.类的实例化

(1)我们现在已经能用C++写出一个基本的类了,那我们写出来类以后,类该如何来调用呢?

首先我们要明确两个概念,什么是声明,什么是定义?

声明:是告诉编译器类型和名称,并没有开辟空间。

定义:是分配内存集体实现,开辟了空间。

而我们所实现的类就是一个声明,计算机并没有为其开辟空间也就是说我们实现的类是一个类似于C语言结构体的一种自定义类型,只不过这种自定义类型里面不仅仅包括对象还有方法,我们需要将类实例化后就可以调用里面的方法和对象。

(2)我们如何将类实例化并且调用呢?

(3)在C++内置类型有其固定的大小,那我们的类类型大小该如何计算呢?

他与结构体一样需要遵循内存对齐的原则,需要注意的是,类中的方法不计算在类类型的大小中。

内存对齐的规则:

(1)偏移地址从0开始。

(2)看默认对齐数来选择对齐位置(VS默认对齐数为8,变量大小为变量对齐数,两者比较取小的)。

(3)最后看所有数据占的字节数是不是该类型中最大的默认对齐数的整数倍,不是的话要凑够整数倍。

那么为什么要内存对齐呢?

其实他的本质是利用空间换时间来提高访问效率。例如某平台下一次读取4个字节数,如果没有默认对齐的话可能一个数据就要读取两次,而我们利用内存对齐虽然会浪费一些空间但是提高我们读取数据的效率。

3.this指针

3.1this指针的引入

我们在实体化两个不同的对象后,调用类中的方法,该如何区分是谁调用的呢?这里我们引入一个this指针的概念来解决问题。

3.2this指针的概念

(1)编译器编译后,类的成员函数都会在形参的第一个位置加上一个指针叫this指针,该指针存储使用成员函数的对象的地址,也就是指向该对象。

(2)类的成员访问成员变量,本质都是通过this指针来访问的。

(3)C++规定不能在实参和形参的位置显示写this指针,但可以在函数体内显示使用this指针,this指针的指向不能改变。

4.类中的默认成员函数

C++提供了6个常用的函数作为默认函数来供用户使用。

4.1默认成员函数的分类

(1)初始化和清理:构造函数、析构函数。

(2)拷贝复制:初始化创建对象、赋值重载。

(3)取地址运算符重载:普通对象取地址、const对象取地址。

4.2构造函数

4.2.1构造函数的功能

构造函数函数,任务是实例化和初始化对象,会在实例化对象的时候自动调用。

4.2.2构造函数的特点

(1)构造函数的函数名和类名相同。

(2)构造函数不需要有返回值(C++规定构造函数不需要写返回值)。

(3)对象实例化的时候会自动调用构造函数。

(4)构造函数可以重载。

(5)编译器没有显示定义构造函数函数编译器会生成一个无参的默认构造,显示写了构造函数就不会再生成了。

(6)默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说不一定会初始化,看编译器对于自定义类型的成员变量要求调用这个成员的默认构造函数初始化,若没有默认构造函数则会报错。

(7)无参构造、全缺省构造我们不写编译器默认生成的都叫默认构造,他们三个只能存在一个,无参构造和全缺省构造虽然可以构成重载但可能会有歧义,总结:不传参的就叫默认构造。

4.2.3构造函数的初始化列表
4.2.3.1初始化列表的引入

如果向上面自定义类型中没有默认构造函数,但有构造函数的重载我们也可以通过初始化列表来初始化。

4.2.3.2初始化列表特点

(1)初始化列表,第一个初始化成员变量前加冒号,其余加逗号,每个成员后面跟上一个括号,里面是初始化的值也可以是表达式和变量。

class A
{
public:
    A(int x, int y)
    {
        _x = x;
        _y = y;
    }
private:
    int _x;
    int _y;
};
​
class B
{
public:
    //使用初始化列表初始化
    B()
    :a1(1,2)
    ,a2(1,2)
    {
​
    }
private:
    A a1;
    A a2;
};

(2)每个成员变量只能在初始化列表中出现一次,语法上理解,初始化列表是每个成员变量初始化的地方。

(3)引用成员变量、const变量、没有默认构造的自定义类型的成员变量,必须放在初始化列表初始化否则会报错。

class B
{
public:
    B(int b)
        :a(b)
        , b(2)
    {
​
    }
private:
    int& a;
    const int b;
};
​
int main()
{
    int b = 1;
    B a = b;
    return 0;
}

(4)C++11支持成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表的初始化成员所用。

class B
{
public:
    B()
    {
​
    }
private:
    //非常量引用的初始化必须是左值
     int* const&  a = (int*)malloc(12);
    const int b = 2;
};
​
int main()
{
    B a;
    return 0;
}

(5)尽量使用初始化列表,进行初始化,因为即使不在初始化列表初始化的成员也需要走初始化列表。

(6)初始化列表初始化的顺序是按照成员变量定义的先后来区分的。

(7)只要是构造函数都可以有初始化列表,包括重载的构造函数。

有了以上知识的铺垫我们自定义类型没有默认构造的情况也得到了解决

4.2.4构造函数初始化的流程图

4.3析构函数

4.3.1析构函数的功能

析构函数的功能是清理资源,用来释放手动申请的空间

4.3.2析构函数的特性

(1)析构函数名是在类名前加上~。

(2)无参返回值不用加void

(3)一个类只能有一个析构函数。没有显示定义,会自动生成默认的析构函数。

(4)对象生命周期结束时,系统会自动调用析构函数。

(5)不写生成的析构函数,对内置类型不做处理,自定义类型会调用它本身的析构函数。

(6)如果显示写析构,对自定义类型的成员也会调用它的析构。

(7)如果类中没有申请资源时候,析构函数可以不写,直接使用编译器生成的析构函数。

(8)后定义的先析构。

4.4拷贝构造

4.4.1拷贝构造函数的功能

是将一个类类型的队象拷贝给另一未初始化的对象

4.4.2拷贝构造的特性

(1)拷贝构造是构造函数的一个重载。

(2)C++规定自定义类型的对象进行拷贝必须调用拷构造,这里自定义类型的传值传参和传返回值都会调用拷贝构造来完成。

(3)拷贝构造的第一个参数必须是类类型的引用,使用传值会引发无穷递归调用,拷贝构造可以有多个参数,但第一个必须是类类型的引用,后面的参数必须有返回值,否则不是拷贝构造。

(4)若没有显示定义拷贝构造,编译器会生成自动拷贝构造函数,自动生成的拷贝构造对内置类型的成员会完成浅拷贝,对自定义类型会调用自己的拷贝构造。

深拷贝:有手动申请的空间,复制的时候要给需要复制的对象新开空间。

浅拷贝:按字节依次复制

(5)无指向资源的自动生成的就够了。

注:该有手动申请的资源拷贝的时候也要申请新的空间不然析构两次会报错。

4.5赋值运算符重载

4.5.1赋值运算符的功能

用于完成两个已经存在的对象的初始化。

4.5.2赋值运算符的地址

(1)是一个运算符重载,必须是成员函数,建议重载的形参写成const类型的引用,这样可以减少一个拷贝构造的调用。

(2)要有返回值这样可以支持连续复值。

(3)没有实现会自动生成一个默认的赋值运算符的重载,但是是浅拷贝,自定义类型会调用其本身的赋值运算符重载。

(4)如果没有手动申请资源,默认生成的赋值运算符就够用了。

4.6取地址运算符重载

4.6.1const成员函数

(1)将const修饰的成员函数,称为const成员函数,const成员函数放到成员函数列表的后面

//该例子没有任何意义,只是让大家掌握用法
class A
{
public:
    int Add(int x, int y)const
    {
        
    }
private:
    int _x;
    int _y;
};

(2)const其实是修饰的隐含的this指针指向的空间,使该空间的值不能修改。

4.6.2取地址运算符的功能和特性

取地址运算符分为普通取地址运算符重载和const运算符重载,它的作用是返回对象的地址,一般自动生成的就够了不需要我们自己实现在此我们就不多讲解了。

5.类型的隐式转换

(1)C++支持内置类型与类类型的转换,需要有相关内置类型为参数的构造函数。

(2)构造函数前加上explicit就不在支持隐式类型转换。

(3)类类型的对象之间也可以通过相应的构造函数相转换。

#include<iostream>
class A
{
public:
    A(int x)
    {
        ;
    }
    A(int x,int y)
    {
        ;
    }
    A(A& x)
    {
        
    }
private:
    int _x;
    int _y;
};
class B
{
public:
    B(A a1)
    {
​
    }
};
​
int main()
{
    //单参
    A a1 = 1;
    //双参
    A a = {1,2};
    //自定义类型-》自定义类型
    B a2 = a1;
    return 0;
}

6.static成员

(1)用static修饰的变量,称为静态成员变量,要在类外进行初始化。

(2)静态成员变量为所有类对象共享存放在静态区。

(3)静态成员函数没有this指针,所以只能访问静态成员。

(4)非静态成员函数,可以访问其他的静态成员变量和静态成员函数。

(5)突破类域可以访问静态成员,可以通过类名::静态成员或对象静态成员来访问静态成员变量。

(6)静态成员变量也是类的成员,受访问限定符限制。

(7)静态成员变量不能再声明位置给缺省值,疑问缺失值是构造函数初始化列表,静态变量相当于一个受类域限制的全局变量。

class Data
{
public:
    //静态变量必须在类内部定义会报错
    //int Data::_a = 10;
    static int Gat()
    {
        //静态成员函数只能引用静态成员变量
        //_b;
        return _a;
    }
    int Gat()
    {
        //普通成员变量可以调用静态也可以调用非静态
        _a++;
        _b++;
        return _a;
    }
​
private:
    static int _a;
    int _b = 20;
};
//静态变量必须在类外部定义
int Data::_a = 10;
​

7.友元

(1)友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数或类声明前加friend,放到一个类的内部的任意位置。

(2)外部友元函数可以访问私有和保护成员,友元函数仅仅是一种声明,不是类的成员函数。

(3)友元可以在类定义的任何地方声明。

(4)一个函数可以是多个类的友元。

(5)友元类中的成员函数都可以是另一个友元的类。

(6)友元无交换性。

(7)友元无传递性。

(8)友元不宜多用会增加耦合度,破坏封装。

8.内部类

(1)把B类定义在A类里面,B类默认是A的友元,可以用A的成员变量

(2)如果一个类定义在另一个类的内部,这个类叫内部类。内部类是一个独立的类,跟定义在全局相比,他只受外部类类域的限制和访问限定符的限制,所以外部类定义的对象不包含内部类。

(3)内部类默认是外部类的友元

(4)内部类本质也是一种封装,当A类跟B类紧密联系,A类实现出来主要是给B类使用的,那么可以考虑把B类设置为A的内部类,受访问限定符的影响。

#include<iostream>
class A
{
public:
​
    int _year;
    class B//B可以默认用A的成员变量
    {
    public:
        B()
    {
    }
        B(A a1)
        {
            //用了A中的成员变量
            a1._year ;
        }
    };
​
​
};
int main()
{
    A d1;
    //定义B的类型
    A::B d2;
    return 0;
}

9.匿名对象

匿名对象的生命周期是改行,如果被const引用后可以延长生命周期,与所在局部域的变量生命周期一样,可以当自定义类型的缺省值。

class Data
{
public:
private:
    int _b = 20;
};
​
​
int main()
{
    //匿名对象初始化为1,生命周期是该行。
    int(1);
    //无参也要加括号生命周期是该行
    Data();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值