C++初阶【类】和【对象(实例)】

本文介绍了C++中面向对象和面向过程编程的概念区别,通过外卖应用案例阐述。还详细讲解了C++中的类定义、访问限定符、类的实例化以及类对象的存储方式,强调了封装和命名规范的重要性。
摘要由CSDN通过智能技术生成

一、面向对象和面向过程

这是C++第二期,如果想更好得学习C++我们需要理清面向对象和面向过程这两个概念。

C语言主要是面向过程编程,C++基于面相对象编程,java面向对象编程,那么具体什么面向过程和面向对象呢?我们接下来拿外卖软件来举例说明一下:

如果你站在C语言设计的角度,那么你需要考虑整个程序运作的流程就是三个: 点餐,接单,派送

如果你站在C++设计的角度来想,那么就需要考虑整个过程中涉及到的对象即: 外卖员,商家,用户,我们会根据各个对象的需求去进行相应的处理。

这就是面向过程和面向对象的区别。


二、C++中的类

C++兼容C语言,结构题用法可以继续使用,同时也升级成了类。

类型升级为类名

(一)类的定义

class ClassName
{
    //类体:包括成员函数和成员变量
}

         看这个格式很容易让人联想到C语言中的结构体,不同的是这个类里面不仅仅可以存放变量,还可以存放函数,这便是C++的关键,这样做可以隐藏代码细节,对权限进行很好的限制,这既是他的优点也是他的优点,损失一部分灵活性,但是使得代码整体变得更加可控更加规范。

        其实C++中仍然兼容stuct定义类的方法,但是他和class又有些不同,具体体现在访问权限上:

1、在struct中,默认的访问权限是public,即结构体中定义的成员变量和成员函数默认是公共的。

2、在class中,默认的访问权限是private,即类中定义的成员变量和成员函数默认是私有的。

public和private都是C++中的访问限定符接下来我们对其进行讲解。

(二)访问限定符

访问限定符分为三类

  1. public(公有)
  2. protected(保护)
  3. private(私有)

1、特性:

  1. public修饰的成员在类外可以直接访问
  2. pro 和 pri 修饰的成员在类外不能直接被访问
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现结束
  4. 如果没有访问限定符,那么到类的作用域结束时结束
  5. class默认访问限定符是私有的
  6. struct默认访问限定符是公有的

注意:访问限定符只有在编译时有用,当数据映射到内存后。没有任何访问限定符上的区别

加上访问限定符后可以有效防止越界访问的情况发生。

2、类的两种定义方式

class为定义类关键字,ClassName为类的名字,花括号是圈定类的主体,类的最后的分号不能省略。

在类里面定义的内容默认为内联

1)类中的函数声明和定义放在一起
//声明和定义全部放在
//学生类
class Student
{
public:
	//显示基础信息
	void ShowInformation(void)
	{
		cout << _name << _sex << _age << _num << endl;
	}
public:
	char*	_name;	//姓名
	char*	_sex;	//性别
	int		_age;	//年龄
	int		_num;	//学号
};
2)声明在类中,定义在功能文件中
//声明放在头文件中
//学生类
class Student
{
public:
	//显示基础信息
	void ShowInformation(void);
public:
	char* _name;	//姓名
	char* _sex;	//性别
	int		_age;	//年龄
	int		_num;	//学号
};
#include "Student.h"
void Student::ShowInformation(void)//注意这里成员函数名前要加类名
{
	cout << _name << _sex << _age << _num << endl;
}

定时格式如下:

void ClassName::FunctionName(void) 
{
     //函数体
}

声明和实现为什么要分开写?

现在开始写项目了,你会发现我们一般都要写一个点cpp文件,对应的还得有一个h文件,那么为什么在C++中我们要这么做?

.h就是声明,.cpp就是实现,而所谓分离式实现就是指“声明”和“定义”分别保存在不同的文件中,声明保存在.h文件、定义保存在.cpp文件中。

那么将声明和定义分离有什么意义吗?

首先从非分离式写法(声明的同时给出定义)看,其内容一般保存在.h文件中,以供多个源文件引用。

但是将定义放在头文件,那么当多个源文件使用#include命令包含此类的头文件便会在链接阶段出现“multiple definition”链接错误!

那么想让多个文件使用此头文件,又不引发链接的“multiple definition”错误该怎么办呢?

分离式的实现便可以解决这个问题。因为.h文件中只包含声明,即使被多个源文件引用也不会导致“multiple definition”链接错误。所以分离式实现增强了命名空间的实用性。

 

注:类的范围内编译器会向下寻找,也就是在整个类体中寻找,不仅仅是向上寻找,这就是为什么变量在函数下仍能使用的原因。且如果在类里面定义,编译器会默认为内联类型。

(三)类中变量命名风格

在实际工程中,可能会出现局部变量和类中成员变量重名的情况,为了避免这种状况呢,一般会根据公司习惯统一给成员变量前加上“ _ ”下划线,这样做有以下几点好处:

  1. 区分成员变量和局部变量:通过在私有成员变量前加上下划线,可以方便地区分成员变量和局部变量,避免命名冲突。
  2. 提高代码可读性:在阅读代码时,可以快速识别出哪些是成员变量,哪些是局部变量,有助于更好地理解代码。
  3. 避免错误赋值:在一些情况下,如果不小心将成员变量当作局部变量来使用,可能会导致错误的赋值或逻辑错误。通过加上下划线,可以降低这种错误的可能性。

举例:

class Data
 { 
public: 
    void Init(int year,int month,int day) 
    { 
        year = _year;
     } 
private: 
    int _year;//如果不加"_"会使得INit函数在调用时出现自己自己赋值的问题 
    int _month;//声明 int _day; 
}
虽然这种命名约定并不是强制的,但许多公司和项目都有自己的编程规范和命名约定,以提高代码的一致性和可维护性。因此,在一些公司中,会要求遵循这种命名约定来编写代码。
C++命名风格通常遵循以下几个原则:
  1. 驼峰命名法(CamelCase):每个单词的首字母大写,除了第一个单词。例如:MyClassName、UserInput。
  2. 下划线命名法(snake_case):单词之间用下划线分隔,所有字母小写。例如:my_class_name、user_input。
  3. 混合命名法(mixedCase):驼峰命名法和下划线命名法的结合,例如:myClassName、userInput。
  4. 类名使用PascalCase:每个单词的首字母大写,除了第一个单词。例如:MyClassName。
  5. 成员变量和成员函数使用camelCase或snake_case:根据其作用域和可见性来决定。例如:m_myVariable、get_userInput。
  6. 常量使用全大写字母和下划线:例如:MAX_VALUE、MIN_VALUE。
  7. 函数名使用camelCase或snake_case:根据其作用域和可见性来决定。例如:calculateAverage、getUserInput。
  8. 类型别名使用snake_case:例如:typedef int MyInt;
  9. 宏定义使用大写字母和下划线:例如:#define MAX_VALUE 100
  10. 注释使用单行注释(//)或多行注释(/* */)。

总之,C++命名风格要求简洁、清晰、一致,避免使用模糊不清或者过于复杂的命名。在实际编程过程中,可以根据项目需求和个人喜好选择合适的命名风格。

(四)类的作用域

在C++中,类的作用域指的是类的成员变量、成员函数以及嵌套类的可见性和访问权限。类的作用域可以分为以下几个部分:

  1. Public作用域:类中定义的公有成员(public members)可以在类的外部被访问,包括成员函数和成员变量。公有成员可以被类的对象和外部代码访问。

  2. Private作用域:类中定义的私有成员(private members)只能在类的内部被访问,外部代码无法直接访问私有成员。私有成员通常用于封装类的内部实现细节,提供更好的封装性和数据安全性。

  3. Protected作用域:类中定义的保护成员(protected members)可以被派生类(子类)访问,但不能被外部代码访问。保护成员通常用于实现继承中的数据共享和扩展。

  4. 友元函数和友元类:友元函数和友元类可以访问类的私有成员,即使它们不是类的成员。友元函数和友元类可以在类的外部声明并定义,但具有访问类的私有成员的权限。

类的作用域规定了类成员的可见性和访问权限,有助于实现信息隐藏和封装,提高代码的安全性和可维护性。通过合理设计类的作用域,可以控制成员的访问权限,防止非法访问和错误使用,同时提供接口供外部代码调用。

封装(Encapsulation)是面向对象编程中的一种重要概念,指的是将数据和操作(方法)封装在一个单元(类)中,并对外部隐藏对象的内部实现细节,只暴露必要的接口供外部访问。封装通过将数据和方法捆绑在一起,形成一个独立的单元,提高了代码的可维护性、可复用性和安全性。

在程序编写中,封装的作用包括但不限于以下几个方面:

  1. 隐藏实现细节:封装可以隐藏对象的内部数据和实现细节,只暴露公共接口给外部使用,降低了对象的耦合性,使得程序更加模块化和易于维护。

  2. 数据保护:通过封装,可以将数据访问限制在类的内部或通过特定的接口进行访问,从而保护数据不受非法访问或修改,提高了数据的安全性。

  3. 代码复用:封装可以将相关的数据和方法组织在一起,形成独立的类,可以在不同的地方重复使用,提高了代码的复用性和可扩展性。

  4. 简化接口:封装可以将复杂的实现细节隐藏起来,对外部提供简单的接口,降低了使用者的认知负担,使得代码更易于理解和使用。

  5. 实现信息隐藏:封装实现了信息隐藏的原则,即将对象的内部状态和行为封装在一起,只暴露必要的接口,使得对象的使用者无需关心对象的具体实现细节,从而降低了系统的复杂度。

(五)类的实例化:用类创建对象的过程

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;

2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

其实“类和对象”这是翻译后的结果,翻译之前它们是“class and object”,object有“实例”的意思,所以说这里将它理解为“类和实例”更为贴切更为合理,但是可能是大家都想有对象吧,前一种说法相对来说流向更广泛。

(六)类对象的存储方式:

这里直接说结论结论:一个类的大小,对,类是可以被衡量大小的,实际结果就是该类中“成员变量大小”之和;其中这里还要考虑内存对齐,根据平台的要求和编译器的规则,对类的大小进行内存对齐。通常情况下,成员变量的大小会按照最大对齐要求的成员变量进行对齐。成员函数放在公共区域因此不会被计算在内。空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象,这一过程称作“占位”。如果类中包含虚函数,编译器会为该类生成一个虚函数表(vtable),并将指向虚函数表的指针作为类的隐藏成员。虚函数表的大小也会计入类的大小中。如果类是派生类,那么其大小还会受到基类的影响。派生类的大小包括基类的大小、派生类新增的成员变量以及可能的填充字节。

成员变量是私有的东西每个人都需要有的基础设施,而成员函数相当于公共设施,有一个谁要用谁去取就可以,不然每个人都拥有将会太占用空间。

 三、示例事件引入

在C环境下,当使用者在想调用栈顶元素时使用“TOP”调用,但无法确定“TOP”具体是指向栈顶元素还是栈顶后一位会导致访问到随机值,如果该函数创建者添加了对应的注释还好说,如果缺少相应注释,调用该函数还需要查看该函数定义,这会使得开发效率变低,这便是C语言的缺陷;

但在C++中使用访问限定符将会避免此类问题:

//实例
typedefy int DataType;
struct Stack
{
void Init(size_t capacity)
{
    _array = (DataType*)malloc(sizeof(DataType) * capacity);
    if (nullptr == _array)
    {
        perror("malloc申请空间失败");
        return;
    }
    _capacity = capacity;
    _size = 0;
}
void Push(const DataType& data)
{
    // 扩容
    _array[_size] = data;
    ++_size;
}
DataType Top()
{

    return _array[_size - 1];
}
void Destroy()
{
    if (_array)
    {
        free(_array);
        _array = nullptr;
        _capacity = 0;
        _size = 0;
    }
}
    DataType* _array;
    size_t _capacity;
    size_t _size;
};
int main()
{
    Stack s;
    s.Init(10);
    s.Push(1);
    s.Push(2);
    s.Push(3);
    cout << s.Top() << endl;
    s.Destroy();
    return 0;
}

本次分享就告一段落了谢谢大家观看,这就是我学习过程的全部理解,很荣高兴和大家进行分享,如果内容对你理解有帮助的话欢迎点赞收藏,如果有疑问或者发现问题欢迎评论交流,对C++感兴趣的朋友可以关注一下,更新后可以及时收到消息。谢谢大家!!

————@寒雒

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒雒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值