C++类与对象(1)—初步认识

目录

一、面向过程和面向对象

二、类

1、定义

2、类的两种定义方式 

3、访问限定符

4、命名规范化 

5、类的实例化

6、计算类对象的大小

7、存储方式

三、this指针 

1、定义 

2、存储位置

3、辨析

 四、封装好处


一、面向过程和面向对象

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

面向过程和面向对象是两种主要的编程范式。它们的主要区别在于如何组织代码和处理数据。

1. 面向过程编程:

面向过程编程是一种编程范式,它以过程(也称为函数)为中心,数据和过程是分开的。在面向过程编程中,程序是一系列被调用的函数或过程,数据被视为辅助函数运行的附属物。这种编程范式的主要特点是流程化,按照事物处理的逻辑顺序来编写程序。

例如,假设我们要编写一个程序来制作一杯咖啡。在面向过程的编程中,我们会创建一个函数来热水,一个函数来研磨咖啡豆,一个函数来混合水和咖啡,等等。每个函数都有一个明确的任务,并按照特定的顺序执行。

2. 面向对象编程:

面向对象编程是一种编程范式,它将数据和处理数据的函数组合成一个整体,称为“对象”。在面向对象编程中,程序是由对象组成的。每个对象都包含数据(称为属性)和一组处理数据的函数(称为方法)。这种编程范式的主要特点是封装性,继承性和多态性。

还是以制作咖啡为例,面向对象编程会创建一个“咖啡”对象,这个对象有一些属性(如水,咖啡豆等)和一些方法(如热水,研磨咖啡豆,混合水和咖啡等)。所有的操作都是通过操作对象的方法来完成的。

二、类

C语言结构体中只能定义变量,而在C++中,结构体不仅可以定义变量,也可以定义函数。

在C++中把结构体升级成了类,而且结构体的名字就是类型。

//C++兼容C结构体用法
typedef struct ListNode
{
	int val;
 //C struct ListNode是类型
	struct ListNode* next;
}LN;

struct ListNode
{
	int val;
 //C++ ListNode是类型
	ListNode* next;
};

1、定义

class className
{
    // 类体:由成员函数和成员变量组成

};  // 一定要注意后面的分号
  • class为定义类的关键字(C语言中用struct),ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
  • 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

2、类的两种定义方式 

在之前数据结构中学过的栈,因为用C语言实现,结构体中只能定义变量,现在我们可以通过C++的方式实现栈,这样在结构体中也可以定义函数。  

第一种:声明和定义全部放在类体中 

注意:

  • 成员函数和成员变量定义在类中的位置没有要求,在调用时会在整个类中查找,不会像类之外使用变量或函数时,编译器只会向上查找。 
  • 成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

这里出现了public和private,他们是访问限定符,稍后进行讲解。 

class Stack
{
public:
	// 成员函数
	void Init(int n = 4)//缺省参数
	{
		a = (int*)malloc(sizeof(int) * n);
		if (nullptr == a)
		{
			perror("malloc fail");
			return;
		}
		capacity = n;
		size = 0;
	}
	void Push(int x)
	{
		//...
		a[size++] = x;
	}
private:		
	// 成员变量
	int* a;
	int size;
	int capacity;
};
int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	st.Push(2);
	st.Push(3);
	return 0;
}

第二种:类声明放在.h文件中,成员函数定义放在.cpp文件中(常用这种)

 头文件中:

//struct Stack无需访问限定符
class Stack//必须有访问限定符,否则报错
{
public:
	// 成员函数
	void Init(int capacity = 4);
	void Push(int x);

private:
	// 成员变量
	int* a;
	int size;
	int capacity;
};

源文件中:

函数名前要加类名 : : (域作用限定符)。

#include "Stack.h"

void Stack::Init(int n)
{
	a = (int*)malloc(sizeof(int) * n);
	if (nullptr == a)
	{
		perror("malloc申请空间失败");
		return;
	}

	capacity = n;
	size = 0;
}

void Stack::Push(int x)
{
	//...
	a[size++] = x;
}

3、访问限定符

在头文件中使用 stuct 定义类:

struct Stack
{
	void Init(int capacity = 4);
	void Push(int x);

	int* a;
	int size;
	int capacity;
};

成功编译: 

在头文件中使用 class 定义类:

class Stack
{
	void Init(int capacity = 4);
	void Push(int x);

	int* a;
	int size;
	int capacity;
};

 结果编译后报错如下:

我们可以发现成员函数无法访问类的成员,这是因为未加访问限定符的class中默认访问权限为私有private,stuct 默认为公有public。

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用

 

 

访问权限符说明:

  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • 如果后面没有访问限定符,作用域就到 } 即类结束。
  • class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

 

4、命名规范化 

我们来看这个代码:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		year = year;
		month = month;
		day = day;
	}
private:
	int year;
	int month;
	int day;
};

 在这个代码中,存在一个问题。在Init函数中,你试图将传入的参数赋值给类的成员变量,但是由于参数和成员变量的名称相同,会导致赋值操作无效。

我们可以选择修改参数名或类成员名,比较好的是在成员名前加上符号_ ,这样小改动保证成员名和函数参数名之间的关系。 

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

5、类的实例化

用类类型创建对象的过程,称为类的实例化

  • 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间、存储类成员变量。

类中成员变量是声明,没有分配空间。

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 d1;//实例化
	d1.Init(2023, 2, 3);
	return 0;
}

 我们通过下面这段代码看看类实例化之后,有没有占用实际的物理空间。

class A2 {
public:
	void f2() {}
};

int main()
{
	A2 aa1;
	A2 aa2;

	//实例化就能打印地址
	cout << &aa1 << endl;
	cout << &aa2 << endl;

	return 0;
}

通过打印地址可以发现,实例化后变量确实被分配空间用来存储类成员变量了。 

 

6、计算类对象的大小

类的大小也遵循与结构体一样的计算方式,详细请看这篇文章:结构体内存对齐

7、存储方式

我们先来看下面这段代码:

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 d1;
	d1.Init(2023, 2, 2);

    //打印d1的大小
	cout << sizeof(d1) << endl;

	return 0;
}

我们由输出结果可知: d1的大小为12字节,由此可以推断出类的对象d1中只存储了成员变量。

 

那么为什么成员变量在对象中,成员函数不在对象中呢?

因为每个对象成员变量是不一样的,需要独立存储;每个对象调用成员函数是一样的,它们被放到共享公共区域(代码段) 。

class A2 {
public:
	void f2() {}
};

int main()
{
    A2 a;
	cout << sizeof(a) << endl;

	return 0;
}

类中只有成员函数,这样定义的对象A2大小是1字节,这1字节不存储有效数据,是用来占位的,标识对象被实例化定义出来了。 

三种类的大小对比

// 类中既有成员变量,又有成员函数
class A1 {
public:
    void f1(){}
private:
    int a;
};

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

// 类中什么都没有---空类
class A3
{};
  • 一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
  • 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

 

三、this指针 

class Date{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << this << endl;
	}

private:
	int _year; 
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2022, 2, 2);
	d2.Init(2023, 2, 2);

	return 0;
}
对于上述类,有这样的一个问题:
Date类中有 Init 成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

1、定义 

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏
的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编
译器自动完成。
如下图所示: 

我们在初始化Init函数中打印this指针,看看this指针有没有发挥作用。

class Date
{
public:
	void Init(int year, int month, int day)
	{
		cout << this << endl;
    //可以显式使用this,不能显式定义this
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2022, 2, 2);
	d2.Init(2023, 2, 2);

	return 0;
}

 输出了两个地址,我们通过调试检查一下这两个地址是不是对象d1和d2的地址。

 

在调试中地址如下,上下两幅图对比可以得知,每次初始化时,this指针会指向参数的地址。

在实际中类的成员函数一般不需要加this指针。 

2、存储位置

this存在哪里?

栈,因为他是隐含形参,vs下在ecx寄存器中。

 通过之前计算类的对象大小不包括this,所以this在栈上,它是一个隐含的形参。

 3、辨析

class Date
{
public:
	void Init(int year, int month, int day)
	{
		cout << this << endl;
    //可以显式使用this,不能显式定义this
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	void func()
	{
		cout << "func()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date* ptr = nullptr;
    ptr->func();

	return 0;
}

程序正常运行: 

 

如果调用这句呢? 

ptr->Init(2022, 2, 2);

结果运行崩溃:

 

下面来解释这两种情况: 

  1. ptr->func()这行代码尝试通过空指针ptr调用成员函数func()。虽然ptr是空指针,但是在这种情况下,由于func()函数没有使用或修改任何成员变量,它可以被静态调用。这意味着它不依赖于具体的对象实例,不需要借助this指针,因此不会引发崩溃。

  2. ptr->Init(2022, 2, 2)这行代码尝试通过空指针ptr调用成员函数Init()。由于Init()函数内部使用了this指针来访问对象的成员变量,而空指针没有有效的对象实例,对空指针的解引用无效,因此在访问this->_yearthis->_monththis->_day时会导致程序崩溃。

同理这段代码也可以正常运行: 

(*ptr).func();

 

 四、封装好处

C++中: 

  1. 数据和方法都封装到类里面
  2. 控制访问方式,愿意给你访问公有,不愿意给你访问私有。

C语言中: 

  1. 数据和方法是分离的
  2. 数据访问控制是自由的,不受限制的

 C++实现栈:

typedef int DataType;
class Stack
{
public:
	void Init()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_size = 0;
	}
	void Push(DataType data)
	{
		CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		_size--;
	}
	DataType Top() { return _array[_size - 1]; }
	int Empty() { return 0 == _size; }
	int Size() { return _size; }
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			DataType* temp = (DataType*)realloc(_array, newcapacity *
				sizeof(DataType));
			if (temp == NULL)
			{
				perror("realloc申请空间失败!!!");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
}; ​
C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/162817.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记录联系ThinkPad T490扬声器无声音但插耳机有声音的解决办法

型号&#xff1a;联想ThinkPad T490&#xff0c;系统Win10 64位。 现象&#xff1a;扬声器无声音&#xff0c;插耳机有声音。且右下角小喇叭正常&#xff0c;设备管理器中驱动显示一切也都正常&#xff08;无黄色小叹号&#xff09;。 解决办法&#xff1a; 尝试了各种方法&a…

基础课7——数据预处理

在智能客服系统中&#xff0c;数据预处理是进行自然语言处理&#xff08;NLP&#xff09;的关键步骤之一。它是对用户输入的文本数据进行分析、处理和转换的过程&#xff0c;目的是将原始文本数据转化为计算机可理解的语言&#xff0c;为后续的智能回答提供支持。 1.什么是数据…

数据结构-插入排序+希尔排序+选择排序

目录 1.插入排序 插入排序的时间复杂度&#xff1a; 2.希尔排序 希尔排序的时间复杂度&#xff1a; 3.选择排序 选择排序的时间复杂度&#xff1a; 所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的…

基于SpringBoot+MyBatis-Plus的教务管理系统

基于SpringBootMyBatis-Plus的教务管理系统 教务管理系统开发技术功能模块代码结构数据库设计运行截图源码获取 教务管理系统 欢迎访问此博客&#xff0c;是否为自己的毕业设计而担忧呢&#xff1f;是否感觉自己的时间不够做毕业设计呢&#xff1f;那你不妨看一下下面的文章&a…

CUDA编程一、基本概念和cuda向量加法

目录 一、cuda编程的基本概念入门 1、GPU架构和存储结构 2、cuda编程模型 3、cuda编程流程 二、cuda向量加法实践 1、代码实现 2、代码运行和结果 有一段时间对模型加速比较感兴趣&#xff0c;其中的一块儿内容就是使用C和cuda算子优化之类一起给模型推理提速。之前一直…

2023年最新软件安装管家目录

最新软件安装管家目录 软件目录 ①【电脑办公】电脑系统&#xff08;直接安装&#xff09;Win7Win8Win10OfficeOffice激活office2003office2007office2010office2013office2016office2019office365office2021wps2021Projectproject2007project2010project2016project2019projec…

python基础练习题库实验3

题目1 编写一个程序&#xff0c;根据以下定价计算成本。 Number of itemsCost1-50每件3美元 邮费: 10美元超过50每件2美元 邮寄&#xff1a;免费 举个例子&#xff1a; 代码 items_num input("Enter the number of items: ") items_num_i int(items_num) ite…

JVM虚拟机:通过日志学习PS+PO垃圾回收器

我们刚才设置参数的时候看到了-XXPrintGCDetails表示输出详细的GC处理日志&#xff0c;那么我们如何理解这个日志呢&#xff1f;日志是有规则的&#xff0c;我们需要按照这个规则来理解日志中的内容&#xff0c;它有两个格式&#xff0c;一个格式是GC的格式&#xff08;新生代&…

Leetcode—206.反转链表【简单】

2023每日刷题&#xff08;三十三&#xff09; Leetcode—206.反转链表 头插法实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* reverseList(struct ListNode* head) {if(head NULL…

PaddlePaddle:开源深度学习平台

深度学习作为人工智能领域的重要分支&#xff0c;正在全球范围内得到广泛应用。而在构建和训练深度学习模型时&#xff0c;选择一个高效、易用且功能强大的开源平台是至关重要的。PaddlePaddle&#xff08;即飞桨&#xff09;作为国内领先的深度学习平台&#xff0c;一直以来都…

13.真刀实枪做项目---博客系统(页面设计)

文章目录 1.预期效果1.1博客列表页效果1.2博客详情页效果1.3博客登陆页效果1.4博客编辑页效果 2.实现博客列表页2.1实现导航栏2.2实现版心2.3实现个人信息2.4实现博客列表2.5博客列表页完整代码 3.实现博客正文页3.1引入导航栏3.2引入版心3.3引入个人信息3.4实现博客正文3.5博客…

C++17中std::variant的使用

可变参数模板类std::variant表示类型安全联合体(type-safe union)。std::variant的实例在任何给定时间要么保存其替代类型之一的值&#xff0c;要么在错误的情况下无值。 与union一样&#xff0c;如果std::variant保存某个对象类型T的值&#xff0c;则T的对象表示形式将直…

【音视频基础】AVI文件格式

AVI文件采用的是RIFF文件结构方式。波形音频wave&#xff0c;MIDI和数字视频AVI都采用这种格式存储。 AVI文件的整体结构如下图所示 构造RIFF文件的基本单元叫做数据块&#xff08;Chunk&#xff09;&#xff0c;每个数据块包含3个部分 4字节的数据块标记&#xff08;或者叫…

动 态 规 划

一、&#xff08;what&#xff1f;&#xff09; 二、&#xff08;why&#xff1f;&#xff09; 三、&#xff08;how&#xff1f;&#xff09; 四、典型例题分析&#xff1a; 例题1&#xff1a;神奇的兔子序列 输入&#xff1a;月数 输出&#xff1a;兔子数 思路&#xff1…

基于机器学习的居民消费影响因子分析预测

项目视频讲解: 基于机器学习的居民消费影响因子分析预测_哔哩哔哩_bilibili 主要工作内容: 完整代码: import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import missingno as msno import warnings warnings.filterwarnin…

Python------列表 集合 字典 推导式(本文以 集合为主)

推导式&#xff1a; 推导式comprehensions&#xff08;又称解析式&#xff09;&#xff0c;是Python的一种独有特性。推导式是可以从一个数据序列 构建 另一个 新的数据序列&#xff08;一个有规律的列表或控制一个有规律列表&#xff09;的结构体。 共有三种推导&#xff…

二十三种设计模式全面解析-当你的对象需要知道其他对象的状态变化时,观察者模式是你的救星!

在软件设计的世界中&#xff0c;有一种设计模式以其简洁而强大的特性闪耀着光芒&#xff0c;它就是——观察者模式&#xff08;Observer Pattern&#xff09;。这个模式它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;为我们创造…

【spring】ApplicationContext的实现

目录 一、ClassPathXmlApplicationContext1.1 说明1.2 代码示例1.3 截图示例 二、FileSystemXmlApplicationContext2.1 说明2.2 代码示例2.3 加载xml的bean定义示例 三、AnnotationConfigApplicationContext3.1 说明3.2 代码示例3.3 截图示例 四、AnnotationConfigServletWebSe…

Git安装与常用命令

Git简介&#xff1a; Git是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或大或小的项目。Git是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源代码的版本控制软件。Git与常用的版本控制工具CVS、Subversion等不同&#xff0c;它采用了分布式…