c++类和对象 类的初步认识 this指针

1. 面向过程和面向对象的认识

  • c语言是面向过程的,关注的是过程,分析出求解的步骤,通过函数调用逐步 解决问题。
  • c++是 基于面向对象 的,关注的是对象,将一件事拆分成不同的对象,靠对象之间的交互完成。
    如果写一个外卖系统

面向过程:

  1. 点餐下单
  2. 接单
  3. 分配骑手
  4. 骑手送餐
    面向过程只会注意过程。

面向对象:

  1. 用户
  2. 商家
  3. 骑手
    面向对象需要注意不同完成,对不同对象的需求和对象间的关系。

2. 类的引入

c语言中,结构体只能定义变量
c++中,结构体也就是类不仅可以定义变量,也可以定义函数。
c++中的类,也就可以认识是c语言结构体的升级
实现栈
c语言:

struct Stack
{    
    int* a;
    int top;
    int capacity;
}
void StackInit(struct Stack* pst)//初始化
{
	//...
}
void StackPush(struct Stack* pst,int x)//入栈
{
	//...
}
int main()
{    
    struct Stack st1;
    StackInit(&st1);
    StackPush(&st1,1);
    return 0;
}
c++
struct Stack
{    
    void Init()//初始化
    {
        a=nullptr;
        top=capacity=0;
    }
    void Push(int x)//入栈
    {
    	//...
    }
    int* a;
    int top;
    int capacity;
}
int main()
{
	//struct Stack st1;
    Stack st1;
    st1.Init();//初始化
    st1.Push(1);//入栈
    return 0;
}

相比于c语言中,c++中实现栈和调用更加方便

  1. c++中可以结构体中定义函数
  2. 入栈和初始化,我们不需要自己传入栈,直接在函数内使用对应的成员变量即可。
  3. 结构体中的函数称为成员函数,使用和c语言中成员变量相似,需要指明是哪个栈的成员函数。

3. 访问限定符

3.1. c++中为什么会出现类的概念

首先我们先看 以前写的结构体 有什么缺点

struct Stack
{
        //成员函数
        void Init()
        {
                a = nullptr;
                top = capacity = 0;
        }
        void Push(int x)
        {
				//..扩容
				a[top++]=x;
        }
        void Top()
        {
        		return a[top-1]
        }
        //成员变量
        int* a;
        int top;
        int capacity;
};

int main()
{
        struct Stack st1;
        Stack st2;
        st2.Init();
        st2.Push(1);
        st2.Push(2);
        st2.Push(3);
        st2.Push(4);

        return 0;
}

我们的成员变量 top 是从0开始的,也就是说 栈顶元素是 top-1的为位置的值
但是 Top 函数里面只有一句return,有人觉得调用麻烦,但是又不清楚我们写的栈 top 从哪里开始,所以他访问栈顶元素可能会这样写

	cout<<st2.a[st2.top]<<endl;

这样会直接造成越界访问


3.2. 访问限定符:

  • 如果我们以后还是和以前一样,让别人随意访问我们定义的内容,很可能会像上面的过程一样出错。
    c++中,为了避免随意访问,就引入了访问限定符
    而这也是c++实现封装的方式:用类创造出的对象,通过访问权限的选择将接口提供给外部用户使用。
    (简单来说就是:用上面定义的结构体(类),定义了一个栈 st1(对象),只允许别人通过我们提供的公共区域使用我们这个对象)
    在这里插入图片描述

上面的 类 创建的 st1 对象,别人对我们的成员变量进行访问导致出现bug,所以我们可以通过访问限定符限制权限,让他们不能使用我们的成员变量,只允许使用成员函数

struct Stack2
{
    //成员函数
    void Init()
    {
        a = nullptr;
        top = capacity = 0;
    }
    void Print()
    {
        cout << a[top - 1] << endl;
    }
    //成员变量
private://私有
    int* a;
    int top;
    int capacity;
};

在成员变量前加上 “private:” 即可,这样的话,在访问成员变量时就会提示不可访问
在这里插入图片描述
ps:我们只对类中的变量进行限定,类中的函数属于公共区域,可以随意访问,这样就能限制别人只能通过我们写的函数间接使用变量,而不是直接对成员变量进行操作。

3.3. 访问限定符的特点

  1. public修饰的成员可以在类外直接被访问。
  2. protected和private修饰的成员不能在类外直接被访问(protected和private类似)。
  3. 访问权限作用域从该访问 限定符出现的位置开始,一直到下一个访问限定符的出现时为止。
  4. 如果后面没有访问限定符,作用域到 "}"结束
  5. class默认权限为 private ,struct 默认权限为 public
  6. 注意:访问限定符只在编译上有用,当数据映射到内存后,没有任何访问限定符的区别(知道私有成员的地址,也可以对其进行操作)。

4. 类的定义

4.1. class关键字

class classname
{
	//类体:由成员函数和成员变量组成
};//和结构体一样,必须有分号

class 为定义类的关键字
class为类的名字
类体中的内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法和成员函数。

4.2. 类的两种定义方式:

  1. 声明和定义全放在类体中,就像我们上面写的方式。需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
  2. 声明和定义分别放在两个文件中
    Stack.h
#include<iostream>
#include<stdlib.h>
class Stack
{
public:
        //成员函数声明
        void Init();

        void Push(int x);

        void Destory();

        int Top();

        //成员变量
private:        //类里面不受访问限定符的限制,限制的是类外面的使用
        int* a;
        int top;
        int capacity;
};

Stack.cpp
注意在每个函数定义前,都需要在函数名前加上 Stack::
类定义了一个新的作用域
类的所有成员都在类的作用域中,在类外定义成员变量时,序要使用 "::"作用域操作符指定成员属于哪个类域。

#include"Stack.h"
void Stack::Init() //Stack 需要放在函数前面
{
        a = nullptr;
        top = capacity = 0;
}
void Stack::Push(int x)
{
        if (top == capacity)
        {
                int newcapacity = capacity == 0 ? 4 : 2 * capacity;
                a = (int*)realloc(a, newcapacity * sizeof(int));
                capacity = newcapacity;
        }
        a[top++] = x;
}
void Stack::Destory()
{
        free(a);
        a = nullptr;
        top = 0;
        capacity = 0;
}
int Stack::Top()
{
        return a[top - 1];
}

class特点:
struct :默认限定符公有(不加访问限定符,所有的内容都可以随意访问)
class :默认限定符私有(不加访问限定符,所有的内容都不可访问)
class:

class Stack
{
        void Init()
        {
                a = nullptr;
                top = capacity = 0;
        }
        void Push(int x)
        {
				//...
        }
        //成员变量
        int* a;
        int top;
        int capacity;
};

在这里插入图片描述
如果想要让class公有,必须给public限定符

class Stack
{
public:
	//...
};

平时建议使用class ,必须使用限定符
ps:类里面的内容不收访问限定符的限制


4.3. 类成员变量命名

定义一个 Date 的类,里面定义一个初始化的函数

#include<iostream>
using namespace std;
class Date
{
public:
        void Init(int year, int month, int day)
        {
                year = year;
                month = month;
                day = day;
        }
private:
        int year;
        int month;
        int day;
};

int main()
{
        Date a;
        a.Init(2023, 10, 15);
        return 0;
}

这里的函数也是采用局部变量优先的原则
也就是说在 Init函数 内,局部变量 year 会给自己赋值,而不是给 a 中的 year 赋值,所以函数结束后,a 中值并未改变。
在这里插入图片描述
所以一般情况下,我们习惯在成员变量前加上 “_”,这样就能避免和传入参数同名 “_year”,“_month”,“_day”。

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

在这里插入图片描述
这并不是规定,是一种好习惯,也就是不一定非要前面加"_",也可以后面加,也可以用 "myear"来表示,怎么方便怎么来。


5.类的实例化

用类创建对象的过程----类的实例化
(简单来说,类相当于蓝图,蓝图无法住人,依据蓝图建造的房子可以住人)

#include<iostream>
using namespace std;
class Date
{
public:
        void Init(int year, int month, int day)
        {
                _year = year;
                _month = month;
                _day = day;
        }
        void Print()
        {
                cout<<_year<<"/"<<_month<<"/"<<_day<<endl;
                cout << _year <<" "<< _month << " " << _day << endl;
private:
        int _year;
        int _month;
        int _day;
};
int main()
{
        Date _year=2013;
        Date _month=10;
        Date _day=18;
        return 0;
}

我们不能直接对类的成员变量在外直接赋值,因为这些数想当于只是声明这个类里面有这个东西,但是并没有为其开辟空间。
只有用类实例化后的对象,才有定义的占有空间的成员变量,才能存储数据。

还是使用上面的类,我们计算类的大小

int main()
{
        Date a;
        Date b;
        cout << sizeof(a) << endl;
        cout << sizeof(Date) << endl;
        return 0;
}

在这里插入图片描述
我们发现,最后的结果只有成员变量的大小,但是包含没有类中函数的大小。
这是因为这里计算的只会计算成员变量的大小,不会计算类内函数的大小。
在上面我们定义了两个 Date 类型,a 和 b,a 和 b 的成员变量时不相同的,a._year 和 b._year 是两个数,但是他们所调用的 Print 函数是相同的。

  • 类在定义的时候,如果每个对象里都有定义函数的话,会导致空间的浪费,对所有对象来说, 内部的函数发挥的作用是相同的,所以没必要定义那么多函数,只需要把类里的函数放在类的公共区域,所有同类的对象都可以调用即可。

我们可以做个测试:

int main()
{
        Date a;
        a.Init(2023, 10, 18);
        a.Print();

        Date b;
        b.Init(2023, 12, 12);
        b.Print();
        return 0;
}

在这里插入图片描述
我们分别对 a,b调用 Init 和 Print 函数,在反汇编内,可以看见他们调用的地址相同,也就是说他们调用的是同一个函数。
注意:a和b 内部定义的变量都不相同

a._year;
b._year

上面两个值不同,地址也不同


6. 类的大小

//1. 类中含函数和成员变量
class A1
{
public:
    void f1()
    {}
private:
    int _a;
};

//2.类中仅有成员函数
class A2
{
public:
    void f2()
    {}
};

//3类中什么都没有
class A3
{};

int main()
{
    cout<<sizeof(A1)<<endl;
    cout<<sizeof(A2)<<endl;
    cout<<sizeof(A3)<<endl;
    
    return 0;
}

在这里插入图片描述

  • 第一个很简单,就是上面所说的,只会计算成员变量的值,不会计算函数的值,int 为 4字节,所以为4。
  • 下面两个结构体开的 1 字节的空间只是为了表示存在这两个结构体,这 1 字节不存储数据,只是占位,表示存在过,但是至于 1 字节存储什么不重要,只是为了区别不同的类,平时我们使用的时候是访问不到这个 1 的。

7.this指针

在说完类中函数时存储在类的公共区域,
那肯定有聪明的小伙伴会问:为什么同一类型下的不同对象,调用类中的函数,可以识别出是使用哪个对象呢
这就全靠我们将要说的 this 指针
首先还是我们的老朋友 Date类


using namespace std;
class Date
{
public:
        void Init(int year, int month, int day)
        {
                _year = year;
                _month = month;
                _day = day;
        }
        void Print()
        {
                cout << _year << "/" << _month << "/" << _day << endl;
        }
private:
        int _year;
        int _month;
        int _day;
};

int main()
{
        Date a;
        a.Init(2023, 10, 18);
        a.Print();
        return 0;
}

在我们调用的时候,我们看到的

void Init(int year, int month, int day);
void Print();
a.Init(2023,10,18);
a.Print();

实际上函数的定义和传入

void Init(Date* this,int year, int month, int day)
void Print(Date* this)
a.Init(&a,2023,10,28);
a.Print(&a);

我们初始化传入3个参数,输出没有传入参数
实际上初始化传入了4个参数,输出传入了1个参数
这个隐藏的参数----this 指针。
这个 this 是编译器自动加入的
函数的定义也就会变成下面的样子

        void Init(Date*this,int year, int month, int day)
        {
                this->_year = year;
                this->_month = month;
                this->_day = day;
        }
        void Print(Date*this)
        {
                cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
        }

this-> 也是编译器自己加的

  • 编译器自己加的,这里如果我们在传入参数位置就加不了了,自己加上去会报错
    在这里插入图片描述
  • this 在实参和形参位置不能明显的写。
  • 但是在类里面可以明显的用
    在这里插入图片描述
    类中的函数可以调用自己类中的函数
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year=year;
		_month=month;
		_day=day;
        Print();
	}
	void Print()
	{
		cout<<_year<<"/"<<_month<<"/"<endl;
	}
private:
	int _year;
	int _month;
	int _day;
}

在这里插入图片描述
这里要注意的是, Init 和 Print 两个函数中分别使用的 this 指针,是指向同一块空间的两个不同指针,也就是说把其他一个置为 nullptr,另一个指针还存在,指向的空间不受影响。

8. this指针使用注意

  1. 问题1
class A
{
public:
    void Print()
    {
        cout<<"Print()"<<endl;
    }
private:
    int _a;
};
//_________________________________________________
int main()
{
    A* p=nullptr;
    p->Print();
    return 0;
}

程序会正常运行,
Printf 函数不在对象内保存,所以编译器会直接调用函数

2.问题2

class A
{
public:
    void Print()
    {
        cout<<"Print()"<<endl;
    }
private:
    int _a;
};

int main()
{
    A* p=nullptr;
    (*p).Print();
    
    return 0;
}

程序会正常运行;
在 vs 中,空指针解引用不会报错,但只是解引用不会报错,如果想要赋值或者进行其他操作,很可能会出错

3.问题3

class A
{
public:
    void PrintA()
    {
        cout<<_a<<endl;
    }
private:
    int _a;
};

int main()
{
    A* p=nullptr;
    p->Print();
    return 0;
}

这个代码就会崩溃
_a 是成员变量,但是传进来的是空指针,对空指针解引用查找成员变量就会崩溃

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值