• 【C++初阶】类与对象(一)


    1、初识面向对象思想

    C语言是面向过程的,将问题分解成多个步骤,通过函数调用一个一个解决。C++是在C语言的基础上增加了面向对象的思想,关注的是对象,将问题分成不同的对象,依靠对象之间交互完成的。

    以洗衣服为例:
    C语言: 洗衣服分为多个步骤逐一完成。
    在这里插入图片描述
    C++: 对象有人、衣服、洗衣粉和洗衣机,通过它们之间的交互完成。
    在这里插入图片描述

    2、类 struct

    2.1 C++中的struct及使用

    对于struct大家并不陌生,在C语言中它是用来定义一个结构体的关键字。如下表示栈:

    typedef struct Stack
    {
    	int* a;
    	int top;
    	int capacity;
    }Stack;
    //声明
    void StackPush(Stack* ps, int x);
    void StackInit(Stack* ps);
    //定义
    void StackInit(Stack* ps)
    {
    	//...
    }
    void StackPush(Stack* ps, int x)
    {
    	//....
    }
    
    int main()
    {
    	Stack st;
    	StackInit(&st);
    	StackPush(&st, 1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    C++创始人对以上这种写法感觉很繁琐,所以对struct进行了升级,变成了类。struct定义的类名就是类型,且可以在类里定义函数。
    如下:

    struct Stack
    {
    	int* a;
    	int top;
    	int capacity;
    
    	void Init()
    	{
    		//...
    	}
    	void Push(int x)
    	{
    		//....
    	}
    };
    
    int main()
    {
    	Stack st;//类名就是类型,前面不需要加struct
    	st.Init();
    	st.Push(1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这样写是不是比前面C语言的代码简洁了不少。当然,通常来说C++很少使用struct ,使用class比较多。

    3、类 class

    3.1 类的定义

    class className
    {
    //
    // 类体:由成员函数和成员变量组成
    //
    }; // 一定要注意后面的分号,与C语言的结构体同

    class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。

    3.2 类的访问限定符

    3.2.1 访问限定符是什么

    类一共有3个访问限定符,分别是:

    public(公有)、protected(保护)、private(私有)

    访问限定符是用来指定类中的成员或者函数是否可在类外被访问的关键字。它是C++实现封装的方式,同时让代码看起来更加完善。

    3.2.2 访问限定符的使用

    使用访问限定符先得知道它们的特性:

    public(公有):修饰的成员在类外可以直接被访问
    protected(保护) 和private(私有):在类外不能直接被访问。(在这里protected和private的功能是类似的)
    访问限定符的作用域从当前开始到出现下一个访问限定符结束,如果后面没有其他访问限定符,类结束就是该访问限定符的作用域结束

    struct Stack
    {
    private://私有
    	int* a;
    	int top;
    	int capacity;
    public://公有
    	void Init()
    	{
    		//...
    	}
    	void Push(int x)
    	{
    		//....
    	}
    };
    int main()
    {
    	Stack st;
    	st.Init();
    	st.Push(1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    类里面的成员函数是公有的,所以类外可以访问到;成员变量是私有的,类外不能被访问。
    在这里插入图片描述

    3.2.3 访问限定符的使用规范

    一般情况下,我们看到的C++代码中的类里面都有访问限定符,比如private和public。大家可能有一个疑惑,访问限定符非写不可吗?其实不是。不写访问限定符也是有区别的,只不过这个区别是在class和struct两个关键字上。class不写访问限定符,默认的访问权限是private;struct不写访问限定符,默认的访问权限是public。这同时也说明了为什么使用struct时类外都可以访问到里面的成员,因为C++要兼容C语言。但是,为了防止错误发生,尽量写访问限定符,这样同时也让代码比较规范。

    3.2.4 访问限定符与封装

    封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
    封装本质上是一种管理,让用户更方便使用类
    在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

    就好比用户使用电脑,只要操作键盘、鼠标和开关机即可,不必知道电脑内部是如何设计的。

    3.3 类做声明和定义分离

    3.3.1 声明和定义分离

    类的定义有两种写法,一种是声明和定义全部写在类里,另一种是分文件声明和定义分离,在做项目的时候或者代码规模较大以后者居多。

    #include 
    using namespace std;
    class Stack
    {
    private:
    	int* a;
    	int top;
    	int capacity;
    public:
    	void Init();
    	void Push(int x);
    };
    /
    void Stack::Init()
    {
    	//...
    }
    void Stack::Push(int x)
    {
    	//...
    }
    /
    int main()
    {
    	Stack st;
    	st.Init();
    	st.Push(1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    定义函数的文件里函数名加上Stack:: 是为了找到Stack类里的函数,因为如果头文件里不止有栈的初始化的声明,可能也有其他的,比如队列的初始化的声明。

    3.3.2 在函数声明的地方也可定义

    C++是可以声明和定义混着写的
    比如:

    class Stack
    {
    private:
    	int* a;
    	int top;
    	int capacity;
    public:
    	void Init();
    	void Push(int x)
    	{
    		//...
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    要注意的一点,定义写在声明里函数变成了内联函数。但是也并不完全是内联函数,因为是否是内联函数取决于编译器。一般来说,声明里定义函数最好是代码比较少的,少的写在声明里可以变成内联函数,提高程序运行效率。

    3.4 类的作用域

    3.4.1 类的成员变量命名规范

    我们在写类的成员变量命名时,要注意规范性。
    如下:

    class Date
    {
    private:
    	int year;
    	int month;
    	int day;
    public:
    	void Init(int year, int month, int day)
    	{
    		year = year;
    		month = month;
    		day = day;
    	}
    	void Print()
    	{
    		cout << year << "-" << month << "-" << day << endl;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    成员变量分别是year、month、day,但是传进来的参数的名字也是year、month、day,赋值的时候是不是感觉很乱。所以建议这样写更好:

    class Date
    {
    private:
    	int _year;
    	int _month;
    	int _day;
    public:
    	void Init(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    };
    int main()
    {
    	Date d1;
    	d1.Init(2023, 10, 23);
    	d1.Print();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述
    成员变量前面加个下划线就好区分了。这里不一定是前面加下划线,后面加或者其他方式都行,只要能够区别参数的名字即可。

    3.4.1 类里支持重载

    一个类就是一个作用域,是支持重载的

    class Date
    {
    private:
    	int _year;
    	int _month;
    	int _day;
    public:
    	void Init()
    	{
    		_year = 2020;
    		_month = 1;
    		_day = 1;
    	}
    	void Init(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    };
    int main()
    {
    	Date d1;
    	d1.Init();
    	d1.Print();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    4、类的实例化

    4.1 类的实例化是什么

    类创建对象的过程就叫做类的实例化。类是对对象进行描述的,它限定了类里面有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。

        Date d1;
    // 类 + 对象
    
    • 1
    • 2

    4.1 为什么有类的实例化

    类里面的成员变量是声明,它没有实际的空间,不能存储数据,必须先要实例化才行。

    4.1.1 类与对象的关系

    一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。类可以比作一张建筑物的图纸,对象就是具体的建筑物,图纸总不能居住吧,但是具体的建筑可以。
    在这里插入图片描述

    5、计算类的对象大小

    5.1 结构体内存对齐规则

    计算类的对象大小之前,我们先来回顾下C语言学过的内存对齐规则:

    1.第一个成员在与结构体偏移量为0的地址
    2.某个成员变量的对齐数 = 编译器默认的对齐数 和 该成员变量大小 中的较小值
    3.前面的成员变量的对齐数要是当前成员变量的对齐数的整数倍
    4.结构体总大小 = 最大对齐数的整数倍
    5.如果结构体嵌套结构体,里面的结构体还是以上步骤进行。外面的结构体也是一样的,只不过整体的总大小是最大对齐数的整数倍,最大对齐数可能是嵌套的结构体的大小。

    class A
    {
    	int year;
    	int month;
    	int day;
    };
    class B
    {
    	char y;
    	int t;
    };
    int main()
    {
    	A a;
    	B b;
    	cout << sizeof(a) << endl;
    	cout << sizeof(b) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    5.2 空类

    空类是没有成员变量的类
    比如:

    class A
    {
    
    };
    int main()
    {
    	A a;
    	cout << sizeof(a) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    a的大小是:
    在这里插入图片描述
    无成员变量的类编译器给一个字节,只是标记它的对象存在而已。

    5.3 类对象存储方式

    先来看一个例子:

    class A
    {
    
    };
    class B
    {
    	void Func()
    	{
    
    	}
    };
    int main()
    {
    	A a;
    	B b;
    	cout << sizeof(a) << endl;
    	cout << sizeof(b) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果:
    在这里插入图片描述
    我们知道,只要是无成员变量的类都是给一个字节,但是为什么有成员函数却不算进去呢?这与类对象的存储方式有关。

    每个对象的成员变量是不同的,但是调用同一个函数。如果类创建多个对象,那么每个对象都要保存一份相同的代码,这样就会浪费空间。所以对象里面只保存成员变量的代码,成员函数放在公共代码段。就好比一间教室有50个学生,每个学生有自己的课本和笔,但是总不能给每个学生安排一个老师吧。

    6、关键字this指针

    6.1 this指针是什么

    先来看一段代码:

    class Date
    {
    private:
    	int _year;
    	int _month;
    	int _day;
    public:
    	void Func(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    };
    int main()
    {
    	Date d1;
    	Date d2;
    	d1.Func(2023, 10, 23);
    	d1.Print();
    	d2.Func(2020, 1, 3);
    	d2.Print();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行结果:
    在这里插入图片描述
    这里设置了两个对象d1和d2,它们调用的是同一个成员函数,问题是函数是怎么知道应该使用哪个对象(怎么找到对象的)

    C++引入了this指针解决了这个问题,this指针是一个成员函数隐藏的形参,对象在调用成员函数时,将对象的地址传给this指针,对成员变量的操作都需要该指针访问。只不过不需要用户自己写出来,C++编译器自动完成。

    	*void Print(Date* this)
    	{
    		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
    	}
    	///
    	d1.Print(&d1);
    	d2.Print(&d2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.2 this指针的特性

    1. this指针的类型:类类型* const
    2. 只能在“成员函数”的内部使用

    this指针存在哪里
    this指针是一个形参,存在栈上,但是VS存在ecx寄存器下,不同的编译器可能不同。

    this指针可以为空吗
    可以为空,但是有些情况不能为空。调用函数时如果函数内部不需要通过this指针指向当前对象且对其进行操作时可以为空,否则会报错。

    6.3 练习

    6.3.1 题一

    该程序运行结果是~~

    class A
    {
    public:
    	void Print()
    	{
    		cout << "Print()" << endl;
    	}
    private:
    	int _a;
    };
    int main()
    {
    	A* p = nullptr;
    	p->Print();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    答案:正常运行
    在这里插入图片描述
    分析:
    p是空指针,很多人以为是空指针然后加箭头就是运行错误,这里不是。Print函数不在对象里,p的作用首先是创建对象,然后检查Print这个成员函数是否在这个类里面。没有,运行错误;反之,正常运行。

    6.3.2 题二

    该程序运行结果是~~

    class A
    {
    public:
    	void PrintA()
    	{
    		cout << _a << endl;
    	}
    private:
    	int _a;
    };
    int main()
    {
    	A* p = nullptr;
    	p->PrintA();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    答案:运行崩溃
    分析:
    这题与上题的代码基本类似,只不过成员函数里要打印的是 _a ,p是空指针,传参给this指针也是空指针,打印 _a 不就对空指针解引用了吗,会导致程序运行崩溃。

  • 相关阅读:
    【sfu】视频接收侧的创建流程
    Ajax了解及请求方式
    手把手带你安装和使用 Git
    处理Centos 7 中buff/cache高的问题
    机器学习概述、特征工程、机器学习算法基础
    【JavaWeb】Servlet系列 --- HttpServlet【底层源码分析】
    伪类应用——
    微信小程序一键获取位置
    合并kubeconfig配置文件
    Android样式和主题背景
  • 原文地址:https://blog.csdn.net/2301_77459845/article/details/133972112