C++类和对象详解(上)

目录

1.inline

1.1概念及定义

1.2编译器中的inline

1.3inline函数的定义与声明

2.类和对象的初认识

2.1类的定义

2.1.1类的格式定义

2.1.2访问限定符

2.1.3类域

2.2类的实例化

2.2.1实例化概念

 2.2.3对象的大小

3.this指针

1.inline

1.1概念及定义

inline是在C++中新引入的内联功能,由inline修饰的函数为内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就不需要建⽴栈帧了,进而提⾼效率

通俗来讲就是函数像宏函数预处理一样展开函数,不会再像平常函数一样在栈区调用。但与宏函数不同的是,内联函数书写更为简单不易出错。以求和函数为例

//宏函数定义
#define Sum(a, b) ((a) + (b))

//内联函数定义
inline int Add(int x, int y)
{
    return x + y;
}

 在定义宏函数时,除非经常刻意使用宏,那么很容易将宏函数写错,并且宏函数语法错误很少,项目中很难检测出来。而对于函数而言,则很少会出现这种情况。

1.2编译器中的inline

但inline对于编译器来说只是一个建议,具体会不会有inline的理想功能,编译器会根据实际情况具体展开,因为C++中并无该规定。inline适用与频繁调用的短小函数,对于递归函数、代码相对较多的函数,加上inline也会被编译器忽略。

也就是说只有当代码较短并且频繁调用时,被inline修饰的函数才会像宏函数一样展开。那代码过长时为什么不被展开呢?从内联函数的优点来说:使用内联函数是为了避免调用函数栈帧进而提高效率,当函数实现简单的功能时,直接像预处理一样展开可直接让实参进行处理。以求和为例:

inline int Add(int x, int y)
{
    return x + y;
}

 普通函数的话,像这样进行调用要先在栈区开辟空间,然后再将实参压栈进入函数,再将和的值进行储存返回,调用完还要销毁函数。但如果我们让函数在调用时直接让实参进行计算处理,则无需再进行空间的开辟。

1.3inline函数的定义与声明

若函数要使用inline类型,则定义与声明是不能分离的,下面以VS2022 x86的环境进行演示:

可以看出在.h文件中声明函数后,在.cpp文件中再定义函数就会存在mian函数中无法找到该函数的问题。这时若仍想用inline,那就是需要在.h文件中就将函数进行定义。如图:

可以看出这样便能成功运行了。那在当内联函数定义和声明分离时为何会找不到该函数呢?在编译过程中:.cpp文件中函数的定义在预处理阶段就会直接展开,后续过程中不会有该函数的地址符号,最终在链接时因无法找到函数的定义而导致运行报错。

2.类和对象的初认识

2.1类的定义

2.1.1类的格式定义

在介绍类的具体格式前,先展示一个类定义的代码来介绍类定义的格式。

#include<iostream>

using namespace std;

class Stack
{
public:
    // 成员函数
    void Init(int n = 4)
    {
        array = (int*)malloc(sizeof(int) * n);
        if (nullptr == array)
        {
            perror("malloc申请空间失败");
            return;
        }
        capacity = n;
        top = 0;
    }

    void Push(int x)
    {
        // ...扩容
        array[top++] = x;
    }

    int Top()
    {
        assert(top > 0);
        return array[top - 1];
    }
    void Destroy()
    {
        free(array);
        array = nullptr;
        top = capacity = 0;
    }
private:
    // 成员变量
    int* array;
    size_t capacity;
    size_t top;
};//分号不能省略

以上便是一个简单的stack类的定义。其中class为定义类的关键字,stack为类名,{}中为类的主体,注意类定义结束时后⾯分号不能省略。

类体中内容称之为类的成员:类中的变量叫做类的属性或成员变量;类中的函数称为类的方法或成员函数。其中的public与private在下一小点讲。

其次为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_ 或者m 开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看要求。下面以日期函数为例:
#include<iostream>

using namespace std;

class Date
{
    //定义成员函数
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    //定义成员变量
    int _year;
    int _month;
    int _day;
};

若在成员变量中不添加辨识符号那么Init方法中便会成如下形式:

void Init(int year, int month, int day)
{
    year = year;
    month = month;
    day = day;
}

使得代码可读性降低,甚至使代码报错。

除了使用class定义类外,struct关键字也可定义类。C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。下面在C++中展现struct在C中的用法:

typedef int ListDataType;
struct ListNode
{
    ListDataType val;
    ListNode* next;
};

可以看出与C中结构体不同的是,C++中可直接使用结构体名,无需进行typedef。

2.1.2访问限定符

C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限
选择性的将其接⼝提供给外部的⽤⼾使⽤。
public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访
问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有
访问限定符,作⽤域就到 }即类结束。
class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public

2.1.3类域

C++中专门为类引入了一个新的类域,类的所有成员都在类的作⽤域中,而在类体外定义成员时,需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。以之前stack类为例:

#include<iostream>

using namespace std;

class Stack
{
public:
    // 成员函数
    void Init(int n = 4);
private:
    // 成员变量
    int* array;
    size_t capacity;
    size_t top;
};

// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
    array = (int*)malloc(sizeof(int) * n);
    if (nullptr == array)
{
    perror("malloc申请空间失败");
    return;
}
    capacity = n;
    top = 0;
}

特别的:之前说到类中的成员函数默认为inline函数,而像这样类成员函数声明与函数定义分类时这种默认内联便会失效,若想要让成员函数成为inline,则需要在类中直接定义inline函数。 

类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全
局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知
道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。

2.2类的实例化

2.2.1实例化概念

类在定义时是没有开辟空间的,可以理解为类在定义时是创建一个自定义变量。比如上面的stack类便是定义一种stack的类型。那么⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。

类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。

⼀个类可以实例化出多个对象,实例化出的对象占⽤实际的物理空间,存储类成员变量。打个⽐ ⽅:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多 少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,⽤设计图修建出房⼦,房 ⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。

下面对上面的Date类进行实例化:

int main()
{
    // Date类实例化出对象d1和d2
    Date d1;
    Date d2;

    d1.Init(2024, 3, 31);

    d2.Init(2024, 7, 5);

    return 0;
}

 2.2.3对象的大小

对象的大小计算规则和C语言中结构体大小计算类似,也需进行内存的对齐。我们以Date类为例:

class Date
{
public:
    //定义成员函数
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void Date::Print()
    {
	    cout << _year << "/" << _month << "/" << _day << endl;
    }
private:

    //定义成员变量
    int _year;
    int _month;
    int _day;
};

其中成员变量都为int,单独计算内存大小为12,但是类中的成员函数大小要计算吗?下面我们通过sizeof来了解Date对象的大小:

可以看出并没有计算成员函数的大小,说明成员函数并不储存在类中。为什么呢?要是成员函数储存在类中,那么每次调用成员函数,都需开辟一次栈空间,但是函数方法却是相同的,不如像全局变量中自定义函数一样通过地址调用函数的方式来进行实现,以免造成内存过度开辟。

了解完类对象大小计算规则后再来看两种特殊的类:

class C1
{
public:
	void f1()
	{
		;
	}
};

class C2
{};

可以看出这里两种类是没有成员变量的,那这两种类的对象大小会为多少?会是0吗?下面仍然用sizeof来看下:

可以看出这两种没有成员变量的类对象大小为1,但是这里的1并不储存任何类实际数据,而是为了占位标识对象存在。不然空间大小为0的话怎么代表该类对象存在呢?

3.this指针

这里的this指针以Date类为例:

#include<iostream>

using namespace std;

class Date
{
public:
    //成员函数
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void Pirnt()
    {
        cout << _year << "/" << _month << "/" << _day;
    }

private:
    int _year;
    int _month;
    int _day;
};
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和
Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了
⼀个隐含的this指针解决这⾥的问题:比如Init实际内部为:
    void Init(Date* this, int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

但是这里this指针是隐性的,创建类成员函数后编译器会自动在形参中添加,我们不可自己进行添加,但可进行使用:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值