【C/C++】类和对象(上):(一)类和结构体,命名规范——两大规范,新的作用域——类域

发布于:2025-08-02 ⋅ 阅读:(21) ⋅ 点赞:(0)


🔥个人主页:艾莉丝努力练剑

专栏传送门:《C语言》《数据结构与算法》C语言刷题12天IO强训LeetCode代码强化刷题C/C++干货分享&学习过程记录

🍉学习方向C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平


 


前言:本专栏记录了博主C++从初阶到高阶完整的学习历程,会发布一些博主学习的感悟、碰到的问题、重要的知识点,和大家一起探索C++这门程序语言的奥秘。这个专栏将记录博主C++语法、高阶数据结构、STL的学习过程,正所谓“万丈高楼平地起”,我们话不多说,继续进行C++阶段的学习。


C++的两个参考文档:

老朋友(非官方文档):cplusplus

官方文档(同步更新):cppreference


目录

正文

一、类

(一)类和结构体

1、类定义的格式

(1)class是定义类的关键字

(2)成员变量和成员函数

(3)类名就是类型,类型定义对象

(4)调用函数

2、类的代码展示

(二)访问限定符

1、访问限定符的概念

2、三种访问限定符

3、对三种访问限定符的补充

(1)对访问限定符定义限制的补充

(2)访问限定符生效的作用域

4、封装本质上体现了严格的规范管理

5、是不是一定要给访问限定符?

(三)命名规范——两大规范

两大命名规范:

(1)驼峰命名法(Camel Case)

(2)蛇形命名法(snake_case)

(3)两大命名规范总结

补充:

(四)类域

1、补充:为什么要做声明和定义的分离?

2、类域的定义

3、类域

4、声明和定义

结尾

本文的完整代码

1、SeqList.h

2、SeqList.cpp

3、Test.cpp


正文

一、类

(一)类和结构体

类的话大家简单理解就是结构体的Plus版,因为C++毕竟是从C里面生长出来的一门语言,很多东西都是,很多东西都还是有千丝万缕的关系的,所以类也是从结构体里面生长出来的,相比于结构体,类更加扩展

1、类定义的格式

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

(2)为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_或者m开头,注意:C++中没有强制要求在成员变量前面或者后面加_或者m开的,只不过是一些惯例,具体还是得看公司的要求;

(3)C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是 struct中可以定义函数,一般情况下我们还是推荐用class定义;

(4)定义在类面的成员函数默认为inline。

总结整理: 

(1)class是定义类的关键字

struct变成了class(结构很相像),下面这个是struct:

而下面这个是class的写法—— 

(2)成员变量和成员函数

如下图所示——

这样我们就不用加什么东西区分了,直接定义在花括号里面。 

(3)类名就是类型,类型定义对象

如下图所示—— 

(4)调用函数

如下图所示——

2、类的代码展示

如果这里放代码的话太占字数,博主就只是挂了截图,我会把本文所有要用到的代码完完整整地放在结尾处,大家点击目录前往即可——

(二)访问限定符

C(面向过程)——数据和方法分离(分开的);

C++(面向对象):面向对象的三大特性——封装、继承、多态(后面两个我们后面再介绍)。

封装的特点:

(1)数据和方法封装到了一起,都在类里面;

(2)访问限定符(限制类外面)。

1、访问限定符的概念

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

(2)public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访 问,protected和private是一样的,以后继承章节才能体现出他们的区别;

(3)访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } (花括号)即类结束;

(4)class定义成员没有被访问限定符修饰时默认为private,struct默认为public;

(5)一般成员变量都会被限制为private / protected,需要给别人使用的成员函数会放为public。

2、三种访问限定符

private(私有)和protected(保护)在现阶段没有区别,在我们介绍继承时再来介绍它们的区别了。private和protected这两个访问限定符用哪个都可以。

3、对三种访问限定符的补充

(1)对访问限定符定义限制的补充

public、private、protected这样的访问限定符定义几个都行,C++对于这方面没有限制,只不过定义那么多显得有些浪费了,我们直接用一个访问限定符解决,比如如果想类内外都可以访问,那我们把这些成员函数都放到一个public下面,private同理——

(2)访问限定符生效的作用域

作用域从访问限定符开始,一直到下一个访问限定符,如果一直没有遇到下一个访问限定符,作用域就到花括号“}”即类结束为止。

4、封装本质上体现了严格的规范管理

C比较自由,而C++有一些规则限制。

一般成员限制为private / protected,需要给别人的就为public。

5、是不是一定要给访问限定符?

(1)class定义的下面如果没给访问限定符,默认是私有的;

(2)(C++兼容C中struct的用(语)法,同时struct升级成类)为了兼容C,struct下面如果没给访问限定符,默认是公有的。

推荐用class,这个和引用一个道理,创造出来就是为了解决C的一些问题嘛。 

补充:

1、struct其实已经变成类了(当做类使用);

2、类名即类型,用结构体名称就可以指代类型(C不允许,要加struct,要方便只能typedef),因而不用加struct了,但一般我们还是用class,以后基本上99%我们都用class;

3、struct里面可以定义函数(升级成类了);

4、

5、类不想用访问限定符限制(想变公有)——用struct(默认公有),

这就是那1%——

(三)命名规范——两大规范

C++定义成员变量(加_或者m——member,即成员)。

谷歌、阿里巴巴的_是加在后面的,百度的_则是加在前面的。

m_ / m + 首字母大写(如mCapacity,再比如m_capacity)。

C++没有统一的标准来规范,主要是C++太早了,问世已经四十多年了,Bjarne博士起初还没这个意识,去规范C++的命名,这个我们作为他老人家的徒子徒孙,还是要尊重历史,老话说得好“子不嫌母丑,儿不嫌家贫”,我们不能忘本,有Bjarne博士这位前人栽树,让我们这些后人乘凉,我们还是要尊重C++官方没有命名规范这个历史。

为了区分形参和成员变量,我们要给成员变量一个明显的标识——

比如前加_或者后加_,就会更好区分。 

两大命名规范:
(1)驼峰命名法(Camel Case)

驼峰命名法是编程中一种常见的变量命名方式。

特点:

单词之间不使用空格或下划线,而是通过每个单词首字母大写来分隔单词,看起来像驼峰的脊背,故此得名“驼峰命名法”。

其实驼峰命名法还分小驼峰命名法(lowerCamelCase)和大驼峰命名法(UpperCamelCase / PascalCase),我们简单了解一下——

1)小驼峰命名法是第一个单词首字母小写,后面每个单词的首字母大写(常用于变量名、函数名);

2)大驼峰命名法是每个单词首字母都大写,包括第一个单词(用于类名、构造函数名等)。

(2)蛇形命名法(snake_case)

蛇形命名法是指单词间用下划线连接的风格,全部小写——

(3)两大命名规范总结

一般情况下,我们用驼峰命名法。

Window系统用驼峰命名法,而Linux系统用蛇形命名法。

很形象——

蛇形命名法则是用_(下划线)分隔开。

补充:

定义在类里面默认为inline(默认就是内联函数,至于最终会不会变成内联函数,这个还是得看编译器我们上篇文章已经做过解释了,VS是10行左右就不会变成内联函数了)。 

(四)类域

我们到现在就已经学了四个域了——局部域、全局域、命名空间域,以及接下来我们要介绍的类域,我们讲过:局部域、全局域是会影响生命周期的,因为它会影响对象存在不同的区域里面,比如说局部的会存在当前函数的栈帧,函数结束就销毁了,静态的、全局的会存在静态区(一直都在),main函数创建之前就存在了,main函数结束了(程序结束)才销毁。

        命名空间域和类域不会影响生命周期,它们只会影响编译的查找规则,同一个域里面不能创建同名变量,不同的域里面是可以创建同名变量的。

我们来举个例子——

下面的栈和队列是不是都有叫Init、push、pop的函数,那么构不构成重载?不构成。

为什么?

前面说了,不同的域可以创建同名变量。这些同名的函数包括参数可以同时存在,因为它们是存在不同的域里面,两个域里面可能会定义同名的函数或者同名的变量,因此不存在两个类的这些变量、函数冲突的,因为它们有一个独立的作用域。

1、补充:为什么要做声明和定义的分离?

目的:是为了方便维护管理我们的代码。

有些时候,我们定义的有些类的代码量可能很大,我们把它们都封装定义到.h文件里面去,那我们看这个类的时候就非常方便了,比如说一个类所有功能实现出来有10000行代码,我们把声明放到.h文件里面,定义放到.cpp文件,那如果我们阅读代码的时候想看一下这个类的框架,我们就可以直接去看.h文件的声明,我们就可以知道有哪些成员、哪些函数,如果我们的注释写得也很好,我们一看,有哪些接口(函数)——成员函数可以被认为是公有的、对外的接口。我们找一些函数具体的实现逻辑可以去看.cpp文件。项目里面有很多不同的功能文件,不同的功能文件实现在.h和.cpp文件里面,然后我们再包含.h就行啦,这些函数就在链接的时候再去其他文件的符号表去找。

如果不做分离,如果把函数的定义都放到.h,多个.cpp包含之后就会有链接冲突的问题,因此在项目里面是必须要去做声明和定义的分离的。

一个是为了方便管理,一个是函数的定义都放到.h会有链接冲突的问题。

注:接口就是函数,像这里的成员函数可以被认为是公有的、对外去对接的接口。

2、类域的定义

(1)类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域;

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

3、类域

C++的类域(如加上void Stack::)——

(1)声明和期望分离了;

(2)除了局部和全局也到类里面去找(既是告诉编译器,也是告诉看代码的人)。

用任何一个函数、变量都要找到出处(声明和定义)。

并且一定要指定类域,否则只会默认在局部搜索和全局搜索。

指定了类域,编译器就认为它是一个成员函数,会去局部和全局搜索,同时会去类域里整体搜索。

注意:类里面定义,既会向上也会向下找,即整个类查找——以前结构体只会向上查找。

4、声明和定义

声明的时候不开空间,定义的时候才开空间出来——

这个我们称之为类实例化对象,什么是类实例化对象?我们下篇文章会详细介绍! 


结尾

本文的完整代码

1、SeqList.h
#pragma once
#include<stdlib.h>

typedef struct SeqList
{
	int* a;
	int size;
	int capacity;
}SL;

inline void SLInit(SL* pls, int n = 4)
{
	pls->a = (int*)malloc(sizeof(int) * n);
	pls->size = 0;
	pls->capacity = 0;
}

void SLPushBack(SL* pls, int x);
int SLFind(SL* pls, int x, int i = 0);
int& SLat(SL* pls, int i);
void SLModify(SL* pls, int i, int x);

class Stack
{
public:
	//u
	void Init(int capacity = 4);
	void Push(int x);
private:
	//u
	int* _a; 
	int _top;
	int _capacity;
};
2、SeqList.cpp
#define  _CRT_SECURE_NO_WARNINGS  1
#include"SeqList.h"

//void SLInit(SL* pls, int n)
//{
//	pls->a = (int*)malloc(sizeof(int) * n);
//	pls->size = 0;
//	pls->capacity = n;
//}

void SLPushBack(SL* pls, int x)
{
	//...
	pls->a[pls->size++] = x;
}

int SLFind(SL* pls, int x, int i)
{
	while (i < pls->size)
	{
		//...
	}

	return -1;
}

int& SLat(SL* pls, int i)
{
	//...

	return pls->a[i];
}

void SLModify(SL* pls, int i, int x)
{
	//...
	pls->a[i] = x;
}

void Stack::Init(int capacity)
{
	_a = nullptr;//malloc
	_top = 0;
	_capacity = capacity;//区分形参和实参
}

void Stack::Push(int n)
{
	//...
}
3、Test.cpp
//数据和方法封装放到了一起,都在类里面
//封装的本质体现了更严格的规范管理
class Stack
{
public:
	//成员函数
	void Init(int capacity = 4)
	{
		_a = nullptr;// malloc
		_top = 0;
		_capacity = capacity;
	}

	void Push(int x)
	{ }

private:
	//成员变量
	int* _a;
	int _top;
	int _capacity;

	//int capacity_;
	////member
	//int m_capacity;
	//int mCapacity;
};

//驼峰法 StackInit 类型 函数, 单词首字母大写开头+单词首字母大写
//      intCapacity   变量   单词首字母小写开头+单词首字母大写

//      stack_init
//      init_capacity

//兼容C的struct的写法
typedef struct A
{
	void func()
	{ }

	int a1;
	int a2;
}AA;

//升级成了类
struct B
{
	void Init()
	{ }

private:
	int b1;
	int b2;
};

struct ListNode
{
	int val;
	//struct ListNode* next;
	ListNode* next;
};

int main()
{
	struct A aa1;
	AA aa2;

	B bb1;
	bb1.Init();

	Stack s1;
	s1.Init();
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);

	//s1.top++;

	return 0;
}

int main()
{

	return 0;
}

往期回顾:

【C/C++】初识C++(三):C++入门内容收尾——const引用,指针和引用关系梳理,inline(内联函数),nullptr替代NULL

【C/C++】初识C++(二):深入详解缺省参数(默认参数)函数重载、引用(重头戏)

【C/C++】初识C++(一):C++历史的简单回顾+命名空间、流插入、命名空间的指定访问、展开问题等概念整理

结语:本文内容到这里就全部结束了, 本文我们在上一篇文章的基础上,继续学习了类和结构体,命名规范——主要是两大规范,新的作用域——类域等概念,从现在一直到学习到模版初阶学完之后,都是些晦涩的概念,还用不起来,到后面我们就能像之前那样,结合起来介绍。


网站公告

今日签到

点亮在社区的每一天
去签到