【C++ 学习总结】- 01 - 类的认识:基本内容

【C++ 学习总结】- 01 - 类的认识:基本内容,第1张

【C++ 学习总结】- 01 - 类的认识:基本内容
  • 一、类的概念
    • 1. 基本概念
    • 2. 对类和对象的理解
    • 3. 面向过程编程 与 面向对象编程
      • (1)面向过程编程的项目组织方式:
      • (2)面向对象编程的项目组织方式:
  • 二、类的基本 *** 作(+)
    • 1. 类的定义
    • 2. 类的实例化
    • 3. 对象的访问
      • (1)通过对象实体访问
      • (2)通过对象指针访问
  • 三、类的访问权限(+)
    • 1. 访问权限修饰关键字
    • 2. 类的封装思想
      • (1)封装的概念
      • (2)封装的要点:
  • 四、this 指针
    • 1. this 指针的概念
    • 2. this 指针的特性
  • 五、静态成员(Static Member)
    • 1. 静态成员变量(Static Member Variable)
      • (1)基本概念
      • (2)静态成员变量的声明与初始化
    • 2. 静态成员函数(Static Member Function)
      • (1)基本概念
      • (2)静态成员函数的定义与调用
  • 六、常量成员(Const Member)
    • 1. 常成员变量(Const Member Variable)
    • 2. 常成员函数(Const Member Function)
    • 3. 常对象(Const Object)
  • 补充要点
    • 1. 类中还可以定义 typdef
    • 2. 类的预先声明使用


一、类的概念 1. 基本概念

  C++ 中的 类 (Class) 可以看做 C 中 结构体 (Struct) 的升级版:可以包含函数的结构体。
  结构体和类都是构造类型,是由用户自己定义的复杂数据类型,是一系列基本数据类型通过用户自己规定的方式组合起来得到的集合体。在 C 中可以使用结构体名定义变量,在 C++ 中可以使用类名定义变量,不同的是,通过结构体定义出来的变量还是叫变量,而通过类定义出来的变量有了新的名称:对象 (Object) 。 除了可以包含函数以外,类还拥有许多结构体所不具有的特性,如:继承、多态等等。

  • 类的成员的别称
      在面向对象的编程语言中,常把类的 成员变量 称为 类的属性 (Property),把类的 成员函数 称为 类的方法 (Method)
  • C++ 语法不提供现成的类
      类是用户自定义的类型,程序中使用时必须提前说明,或者使用已存在的类(别人写好的、标准库中的类等),C++ 语法本身并不提供现成的类的名称、结构或内容。



2. 对类和对象的理解

  类是一个模板(Template),一个用来描绘编程者自创的数据类型的模板。由于只是描绘结构而未实际创建变量,其编译后并不会占用内存空间,因此也不能在定义时对成员变量进行初始化,因为没有地方存储数据。只有在创建对象后,系统才会给成员变量分配内存空间,这时才能对成员变量进行赋值。
  为了更好地理解,我们可以将类比喻成图纸,则对象是根据这个图纸制造的零件。图纸说明了零件的属性(成员变量与成员函数),根据一张图纸可以制造出任意数量性质相同的零件,而根据不同的图纸生产出的零件是不一样的。

  类只是一张图纸,起到说明的作用,不占用内存空间;对象是具体的零件,是实际存在的实体,需要空间来存放。



3. 面向过程编程 与 面向对象编程

  类是一个被广泛使用的概念,C++、Java、C#、PHP 等很多编程语言中都支持类。可以将类看做是结构体的升级版,C语言的晚辈们看到了C语言的不足,尝试加以改善,继承了结构体的思想,并进行了升级,让程序员在开发或扩展大中型项目时更加容易。

(1)面向过程编程的项目组织方式:

  面向过程编程(POP,Procedure Oriented Programming) 中,函数和变量各自离散存在,它们归束到不同的源文件之中,所有源文件结合起来组成一个项目整体。



(2)面向对象编程的项目组织方式:

  相比面向过程编程,面向对象编程(OOP,Object Oriented Programming) 新增了「类」的层级:相互联系的函数与变量根据一定规则打包到不同的类之中,来共同实现一定的功能。一个源文件中包含许多类,所有源文件结合起来组成一个项目整体。


  面向对象编程在代码执行效率上并没有优势,它的主要目的是方便程序员组织和管理代码,快速梳理编程思路,带来编程思想上的革新。
  ( 也就是:代码效率 换 开发效率 )



二、类的基本 *** 作(+) 1. 类的定义

  类的定义通过关键字 < class > 来实现,其语法格式如下所示:

class className {
	access specifier:		// 访问权限修饰 (public, protected, private)
		member Variables;		// 成员变量(属性) 列表
		member Functions();		// 成员函数(方法) 列表		
};	// 分号 ";" 结束类的定义

  成员函数可以直接定义在类体内部,这样就会被自动声明为「内联」的;也可以定义在类体外部,这时需要使用 域解析运算符 < :: > 来指明函数所属于的类。在类外也可以通过关键字 来定义内联的函数,在原定义的前面添加 inline 即可。

  类的定义的完整示例:

================================ 类内定义成员函数 ================================
/* 定义一个 Student 类, 拥有三个成员变量和一个成员函数 */
class Student{
	private:
	    /* 成员变量 */
	    char *	m_name;		// 姓名
	    int 	m_age;		// 年龄
	    float 	m_score;	// 分数
	    
	public:
	    /* 成员函数 */
	    void query (void) {
			printf("-> 学生姓名: %s,  年龄: %d,  成绩: %f \n", m_name, m_age, m_score);
	    }
};

================================ 类外定义成员函数 ================================
/* 定义一个 Student 类, 拥有三个成员变量和一个成员函数 */
class Student {
	private:
		/* 成员变量 */
		char *	m_name;		// 姓名
		int 	m_age;		// 年龄
		float 	m_score;	// 分数
		
	public:
		/* 成员函数 */
		void query (void);
}

/* 在类的外部使用 "::" 定义成员函数 */
void Student::query (void) {
    printf("-> 学生姓名: %s ;  年龄: %d ;  成绩: %.2f \n", m_name, m_age, m_score);
}

/* 在类的外部使用 "inline" 定义「内联的」成员函数 */
inline void Student::query (void) {
    printf("-> 学生姓名: %s ;  年龄: %d ;  成绩: %.2f \n", m_name, m_age, m_score);
}
  • 示例中 Student 是类的名称。类名通常大写首字母,而 成员变量大都以 “m_” 开头,这并不是语法规定,而是约定成俗的写法。类名大写是为了和其他标识符区分开,而以 “m_” 开头既可以一眼看出这是成员变量,又可以和成员函数中的形参名字区分开。这些约定俗成的写法为我们提供了一定程度的规范,使代码更加清晰有条理。

  • { } 内部是类所包含的成员变量和成员函数,统称为 类的成员(Member)。由 { } 包围起来的部分称为 类体,与函数体的概念类似。

  • < public > 是 C++ 中的关键字,仅用在类的定义中,用来修饰类的成员的访问权限为“公开”。(public、protected、private)

  • 在类定义的最后有一个分号 " ; " ,它是类定义的一部分,表示类的定义的结束,不能省略。


2. 类的实例化

  有了 图纸 (类) 之后就可以根据图纸制作 零件 (对象) 了。根据类创建对象的过程叫「类的实例化」,每个对象都是类的一个 具体实例(Instance),拥有类的 成员变量(属性) 和 成员函数(方法)。

	================================ 对象的创建方法 ================================
	1. 一般创建方法
		class Student stu;			// 与 struct 定义变量时相似
		
	2. 省略关键字<class>的创建方法
		Student stu;				// Student 被看做是一种新的数据类型(Data Type),可以用来定义变量
	
	3. 创建对象数组
		Student stu[50];	

  可以看出 通过类创建对象 与 通过结构体定义变量 是十分相似的,二者的本质都是 “通过程序员自定义的构造模板创造程序员自定义的复杂数据类型” 的行为。


3. 对象的访问 (1)通过对象实体访问

  创建对象之后,可以使用 " . " 运算符来访问其成员变量和成员函数,这与访问结构体的成员变量的方式相同。

	================ 通过实体访问对象成员 ================
	/* 创建一个 Student 类的对象 stu */
	class Student stu;
	
	/* 访问成员变量 */
	stu.m_name 	= "张三";
	stu.m_age 	= 16;
	stu.m_score = 82.5f;

	/* 访问成员函数 */
	stu.query();
	

(2)通过对象指针访问

  C 中经典的指针在 C++ 中仍广泛使用,C 中有指向结构体的指针,而 C++ 中有指向对象的指针。C++ 中通过对象指针访问对象成员的方式与 C 中通过结构体指针访问结构体成员的方式相同,也是通过 " -> " 运算符。

	================ 创建对象指针 ================
	/* 使用一般方法 */
	Student stu;				// 使用上文定义的 Student 类
	Student * pStu = &stu;
	
	/* 使用 C++ 中的 new 关键字 */
	Student * pStu = new Student;
		
	================ 通过指针访问对象成员 ================
	/* 访问成员变量 */
	pStu -> m_name 	= "张三"
	pStu -> m_age 	= 16;
	pStu -> m_score = 82.5f;
	
	/* 访问成员函数 */
	pStu -> say();
	



三、类的访问权限(+) 1. 访问权限修饰关键字
  • 类的访问权限通过三个关键字:public (公有)、private (私有)、protected (受保护) 来修饰
    • 被声明为 public 的成员可以被任意访问
    • 被声明为 private 的成员仅可以在类的内部被访问
    • 被声明为 protected 的成员不可以被外部访问,但可以在其派生类的内部被访问
    • 访问保护等级:private > protected > public

  • 如果成员 既不声明为 private 也不声明为 public,则默认为声明为 private 。(类的成员默认声明为 private)

  • 关键字的修饰范围为从关键字开始一直到下一个修饰访问权限的关键字出现为止(没有则一直到类的定义结束)

  • 在类的内部,无论成员被声明为 public、private 或是 protected,都是可以互相访问的,没有限制。
    在类的外部,只能通过对象访问成员,且只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

  • Java、C# 程序员注意,C++ 中的 public、private、protected 只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分。


2. 类的封装思想 (1)封装的概念

  所谓 封装 即:把内部复杂的实现过程与细节等内容隐藏起来,仅对用户提供简洁有效的使用方法。在OOP中,类的封装是指:尽量隐藏对用户无意义的类的内部实现,只向用户提供有用的成员函数(方法)。
  封装是一个广泛存在的概念,是人类的工程项目在发展中日渐复杂伴随着分工协作的日益加强所带来的必然结果。封装的目的旨在实现更好的模块化,其方式主要为 降低耦合 和 简化繁杂:

  1. 降低耦合:封装通过隐藏内部细节,使类的创建者可以规定外部访问类的方式,阻止外部可能的不合法访问以及避免混乱的代码逻辑
      如果对类的属性不做限制而使外部可以肆意访问,那么确实是增强了灵活性,但是灵活性也意味着"不可控性"。对外部开放更多的 *** 作意味着要更加相信它的使用者,但正如那句经典名言:“永远不要相信用户的输入” 所言,我们永远不知道产品的使用者是否奇葩、有多奇葩,在一切未知的情况下如果要最大可能地保证功能的正常运转,就必须默认用户是个绝顶傻瓜。以最低限度的眼光看待用户、以最全面的方式审查过程与细节、以最严格的态度控制访问方式,才能最大程度地杜绝一切非法的、错误的 *** 作及其带来的不利影响,预防未知的、可能的错误和问题。这是现代程序设计的重要规范,是保证模块化设计质量的必要规范,深刻地影响着程序的健壮性。
      同时,由创建者提供规范的访问方式能杜绝使用者的混乱代码逻辑。如果开放内部属性的任意访问,那么属性可以在工程的任何角落以任何可能的(正常或是奇葩的) 方式被修改,势必会影响代码的可读性与可维护性。通过创建者建立一套规范的访问机制能够避免这些烦人的问题,从而提高程序的规范性和可维护性。

  2. 简化繁杂:封装通过隐藏内部实现方法,仅提供简洁的函数接口,提高类的易用性。
      如果你购买一个冰箱,使用时却需要知道如何驱动压缩机,那你肯定会对厂家无以吐槽以至无语,所以最后我们用上的冰箱有简洁的控制按钮,或者是一个先进的图形触控 *** 作界面,亦或是更先进的智能家居APP。
      这就揭示了封装的另一个目的:屏蔽繁杂的实现细节,提供简洁的使用方法。
      用户不需要知道怎么调制信号,怎么驱动压缩机,怎么使用算法控制温度恒定在预定值——这些是制造者们要考虑的问题,无论是实现还是细节,这些统统都是制造者的工作,是用户不需要知道的繁杂又多余的内容;用户需要知道的仅仅是怎么 *** 控触控板来控制冰箱。冰箱制造厂商完成了冰箱的制造,使用冰箱外壳将零部件包裹起来屏蔽了冰箱的内部细节,而后提供了触控面板给用户以简单快捷地使用冰箱,我们使用类即是如此:创建者编写完类,通过private来屏蔽内部细节,再通过public的方法向外部提供简单快捷的使用方法。
      至此我们就形象地理解了封装如何简化繁杂,也明白了现代化模块化设计的思维:我负责我的(制造),你负责你的(使用),我们分工明确。

(2)封装的要点:
  • 类的封装设计规范
      根据 C++ 软件设计规范,“类的成员变量” 与 “只在类的内部使用的成员函数(即:只被成员函数调用的成员函数)” 都建议声明为 private,而只将 “允许通过对象调用的成员函数” 声明为 public。
      简而言之即:① 该向外暴露的接口都应声明为 public,
            ② 不希望外部知道、只在类的内部使用、对外部没有影响的成员都建议声明为 private 。

  • 如果将成员变量都声明为 private,又如何访问他们呢?
      我们可以通过 “设置公开的内部函数” 的方法来实现 “IO”:创建一个 public 方法 set_xxx() 来赋值,创建一个 public 方法 get_xxx() 来读取。赋值函数以 " set_ " 开头,读取函数以 " get_ " 开头是一种默契,旨在使得代码更加清晰易读。



四、this 指针 1. this 指针的概念

  我们已经知道,类只是一个图纸,用来创建对象的图纸,只有对结构描绘的语言,而不具备任何实体。如果我们在类中编写的成员函数需要用到类中的成员变量或成员函数时,就会遇到问题:我们使用类里的成员实质上是 “在通过类创建对象后,使用对象所拥有的成员”,但是我们在编写类时,对象并没有创建,我们也就无从得知 “对象的地址”,也无从访问在未来才会创建的对象的成员。

  于是 C++ 通过添加一个「this 指针」来解决这个问题。this 指针是一个 const 指针,指向 当前对象,也就是正在使用的对象自己。this 指针通过 C++ 新增的关键字 来使用,且只能在类的内部使用,通过它可以访问当前对象的所有成员(因为是在类的内部使用,所以访问不受 public、protected、private 的限制)。

   是一个指针,所以需要通过 -> 来访问其指向对象,语法格式如下:

	// 通过this指针访问成员变量
	this->memVar;

  this 指针的使用示例:

class Student {
	private:
		char *	m_name;		// 姓名
		int 	m_age;		// 年龄
		float 	m_score;	// 分数
		
	public:
	    void query (void) {			// 通过this指针指定访问"本对象"的成员
			printf("-> 学生姓名: %s,  年龄: %d,  成绩: %f \n", this->m_name, this->m_age, this->m_score);
	    }
}

2. this 指针的特性
  • 「this 指针」的书写省略
      眼尖的你或许发现了问题:我们上文的 query(); 成员函数中并没有使用 this 指针,但仍然输出了和这里使用了 this 指针之后相同的结果。这是因为 C++ 默认了在成员函数内对成员变量的直接访问都是对 this 的隐式引用,简单地说,在成员函数内部可以省略this指针的书写,这减少了程序员的工作量。

  • 「this 指针」是隐式参数
      实际上每个非静态的成员函数都会被编译器添加一个隐式的形参,这个形参就是 this 指针,成员函数便通过这个隐式的形参来访问当前对象。这个形参并不出现在代码之中,而是在编译的时候由编译器默默地添加到参数列表中。(可以通过汇编代码看见)

  • 「this 指针」的使用范围
      this 指针的使用范围是非静态成员函数中。this 指针依赖对象实体,而静态成员函数是归属于类的概念,由于其使用不依赖对象的创建,故而无法使用this指针。友元函数不是类的成员,所以没有 this 指针。

  • 「this 指针」的生命周期
      this在成员函数的执行开始前构造,在成员函数的执行结束后清除。

  • 「this 指针」不可修改
      this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的 *** 作都是非法的。

  • 「this 指针」的使用情况:
      ① 在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;
      ① 在参数名和成员变量重名时,使用 this 指针来区分,如 this->age = age (所以我们默契使用 “m_” 开头命名成员变量)



五、静态成员(Static Member) 1. 静态成员变量(Static Member Variable) (1)基本概念

  每个对象都有自己独立的内存空间,这意味着不同对象的成员变量相互独立、互不影响。但有时候我们会有这样的需求:在多个对象之间共享某些数据,使得所有对象都可以自由访问这些数据。这样需求的一个典型场景是计数,以前面的 Student 类为例,如果我们想要知道学生的总数,就可以通过一个共享的数据 “学生数量” 来实现:定义一个共享的变量 “学生数量” 并初始化为0,在每次创建 Student 对象时对这个变量进行自增1 *** 作。显然,我们可以用其他方法比如遍历所有存在的对象等方式来实现相同的效果,但是诸如遍历等方法的繁杂、低效也是显而易见的。
  由此需求,C++ 提出了「 静态成员变量(Static Member Variable)」这一概念。静态成员变量是一种特殊的成员变量,通过关键字 来修饰。静态成员变量属于声明它的类而非由类定义的某个对象,因此无论创建多少个对象,也只会创建一个静态成员变量、分配予一份内存,而所有对象都可以访问储存在这份内存中的静态成员变量。静态成员变量的内存是独立地开辟在所有对象的内存空间之外的,即使不创建对象也可以访问,其与普通 static 变量相同,都是在全局数据区分配分配内存。


(2)静态成员变量的声明与初始化

  静态成员变量通过在变量类型前添加一个关键字 来声明:

	// 静态成员变量的声明
	static varType varName;

  因为类的声明中不能初始化成员变量,而静态成员变量也不属于对象,无法借助对象的创建来初始化,所以其初始化方式为在类外独立地初始化,简称 “类外初始化”。静态成员变量的内存不是在声明类时分配,也不是在创建对象时分配,而是在其类外初始化时分配,因此,没有类外初始化的静态成员变量不能使用
  静态成员变量在类外初始化时不需要再使用 关键字修饰,但是要有数据类型,无论是被 public、protected 还是 private 修饰的静态成员变量都可以通过类外初始化来完成其初始化。静态成员变量的类外初始化同全局变量一样,是写在所有函数之外的。

  类外初始化 的语法格式:

	// 静态成员变量的类外初始化
	varType className::staticMemVar = value;

  静态成员变量的完整使用示例:

================================ 声明示例 ================================
class Student {
	private:
		char *	m_name;		// 姓名
		int 	m_age;		// 年龄
		float 	m_score;	// 分数

	private:
		static int m_allstu;		// 学生总数

	public:
		static float m_allscore;	// 学生总成绩

	public:
		/* 构造函数 */
		Student (char * name, int age, float score) :
			m_name(name),
			m_age(age),
			m_score(score)
		{
			m_allstu++;			// 累计人数
			m_allscore += score;	// 累计总成绩
			printf("-> 新增学生 %s :  年龄 %d,  成绩 %.2f \n", name, age, score);
		}

		void Student_Nums (void) {
			printf("-> 共有学生 %d 名 \n", m_allstu);
		}

		void Average_Score (void) {
			printf("-> 学生平均成绩为:  %.2f \n", m_allstu == 0 ? 0 : m_allscore/m_allstu);
		}
};

================================ 程序示例 ================================
/* 初始化静态成员变量 */
int	  Student::m_allstu   = 0;			// 初始化学生人数
float Student::m_allscore = 0.0f;		// 初始化总成绩

int main ()
{
	/* 创建 Student 类的对象 */
	Student stu1("张三", 16, 82.5);		// 这里只需知道通过构造函数创建了对象并完成了对象的初始化
	Student stu2("李四", 17, 84.5);
	Student stu3("王五", 18, 86.5);
	Student stu4("赵六", 19, 88.5);

	stu1.Student_Nums();		// 查询总人数
	stu1.Average_Score();		// 查询平均成绩

	return 0;
}

================================ 运行结果 ================================
-> 新增学生 张三 :  年龄 16,  成绩 82.50
-> 新增学生 李四 :  年龄 17,  成绩 84.50
-> 新增学生 王五 :  年龄 18,  成绩 86.50
-> 新增学生 赵六 :  年龄 19,  成绩 88.50
-> 共有学生 4-> 学生平均成绩为:  85.50



2. 静态成员函数(Static Member Function) (1)基本概念

  static 不仅可以声明静态成员变量,还可以声明静态成员函数,通过 static 修饰的成员函数就是 静态成员函数(Static Member Function)。普通成员函数可以访问所有成员,而静态成员函数只能访问静态成员。
  编译器在编译普通成员函数时会隐式地增加一个形参 this 用来传递当前对象的地址,所以普通成员函数只能在创建对象后调用,因为它需要当前对象的地址;而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。(特点:没有隐式的 this 指针,可以在对象未创建时使用)但也因为没有 this 指针,没有对象地址,静态成员函数不能访问普通成员变量,只能访问静态成员变量。

  • 静态成员函数与普通成员函数的本质区别
      普通成员函数有 this 指针,可以访问类中任意成员;而静态成员函数没有 this 指针,只能访问静态成员。也因为没有 this 指针,静态成员函数没有对象也可以调用,且可以通过类进行调用(一般这样做,也推荐这样做),而普通成员函数只能通过对象调用。

(2)静态成员函数的定义与调用

  使用 关键字修饰来声明/定义静态成员函数,其语法格式为:

	// 静态成员函数的定义
	static varType funcName (varType varName, ...) {
		// Funtions Content
	}

  静态成员函数由于不依赖隐式的 this 指针,除了通过对象来调用外,还可以通过类来调用:

	// 通过对象调用 静态成员函数
	objectName.funcName(var1, var2, ...);		// 使用 "."
	// 通过类名调用 静态成员函数
	className::funcName(var1, var2, ...);		// 使用 "::"

  通过一个完整的代码示例来理解静态成员函数:

================================ 定义示例 ================================
class Student {
	private:
		char *	m_name;		// 姓名
		int 	m_age;		// 年龄
		float 	m_score;	// 分数

	private:
		static int 	 m_allstu;		// 学生总数
		static float m_allscore;	// 学生总成绩

	public:
		Student (char * name, int age, float score) :
			m_name(name),
			m_age(age),
			m_score(score)
		{
			m_allstu++;				// 累计人数
			m_allscore += score;	// 累计总成绩
			printf("-> 新增学生 %s :  年龄 %d,  成绩 %.2f \n", name, age, score);
		}

		static void Student_Nums (void) {
			printf("-> 共有学生 %d 名 \n", m_allstu);
		}

		static void Average_Score (void) {
			printf("-> 学生平均成绩为:  %.2f \n", m_allstu == 0 ? 0 : m_allscore/m_allstu);
		}	
};

================================ 程序示例 ================================
/* 初始化静态成员变量 */
int	  Student::m_allstu   = 0;			// 初始化学生人数
float Student::m_allscore = 0.0f;		// 初始化总成绩

int main()
{
    printX('*', 32, "程序开始执行", 2, 2);
	
	/* 通过类调用静态成员函数 */
	Student::Student_Nums();		// 查询总人数
	Student::Average_Score();		// 查询平均成绩
	
	/* 创建 Student 类的对象 */
	Student stu1("张三", 16, 82.5);
	Student stu2("李四", 17, 84.5);
	Student stu3("王五", 18, 86.5);
	Student stu4("赵六", 19, 88.5);

	/* 通过对象调用静态成员函数 */
	stu1.Student_Nums();		// 查询总人数
	stu1.Average_Score();		// 查询平均成绩

    printX('*', 32, "程序执行完毕", 2, 2);

    return 0;
}

================================ 运行结果 ================================
-> 共有学生 0-> 学生平均成绩为:  0.00
-> 新增学生 张三 :  年龄 16,  成绩 82.50
-> 新增学生 李四 :  年龄 17,  成绩 84.50
-> 新增学生 王五 :  年龄 18,  成绩 86.50
-> 新增学生 赵六 :  年龄 19,  成绩 88.50
-> 共有学生 4-> 学生平均成绩为:  85.50

要点总结

  • 静态的成员变量,初始化时可以赋初值,也可以不赋值默认初始化为0。

  • static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。

  • 静态成员变量必须初始化,而且只能在类体外进行。

  • 初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。

  • 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 的访问权限限制。通过同一个类的不同对象访问的是同一个内存里的同一个数据。



六、常量成员(Const Member) 1. 常成员变量(Const Member Variable)

  「const 成员变量」和「普通 const 变量」相同,都是被修饰为不可修改的常量,也都是在声明时在前添加 关键字来修饰。const 成员变量的初始化主要有两种方法,一种是通过构造函数的初始化列表,一种是 C++ 11 标准 新增的直接赋值。

  常量定义的语法格式:

	// 写法 1
	const varType varName;		// const 和 varType 都是修饰变量的关键字, 二者位置可以互换, 我们通常采用 const 置前的写法
	// 写法 2
	varType const varName;

2. 常成员函数(Const Member Function)

  常成员函数(Const Member Function) 可以使用类中的所有成员变量,但是不能修改它们的值,这是一个为了 保护数据 而设立的特性。
  常成员函数的声明和定义方法为在函数头部的结尾添加 关键字,其语法格式为:

	// 常成员函数的声明
	varType funcName (varType varName, ...) const;
	
	// 常成员函数的定义
	varType funcName (varType varName, ...) const {
		// Funtion Content
	}

  常成员函数的声明与定义示例:

class Student {
	private:
		char *	m_name;		// 姓名
		int 	m_age;		// 年龄
		float 	m_score;	// 分数

	public:
		/* * * * 声明常成员函数 * * * */
		char * get_name (void) const;
		
		int get_age (void) const;
		
		/* * * * 定义常成员函数 * * * */
		float get_score (void) const {
			return m_score;
		}
};

/* * * * 定义常成员函数 * * * */
char * Student::get_name (void) const {
	return m_name;
}

int Student::get_age (void) const {
	return m_age;
}
  • 如果在常成员函数中修改成员变量:
================================ 代码示例 ================================
/* * * * 定义常成员函数 * * * */
float get_score (void) const {
	m_score = 0.0f;			// 非法地在常成员函数中修改成员变量
	return m_score;
}
================================ 编译结果 ================================
error: assignment of member 'Student::m_score' in read-only object
错误: 在只读对象(指 get_score() )中对成员 Student::m_score 赋值
  • 需要注意:常成员函数的声明和定义都需要加上 关键字

  • 在不同位置的不同含义
      函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改的常量。
      函数体前的 const 用来表示常成员函数,这种函数只能读取而不能修改成员变量的值。


3. 常对象(Const Object)

   不仅可以用来修饰类的成员,还可以用来修饰对象,被 const 修饰的对象称为 常对象(Const Object)。普通对象可以调用任意的成员(不考虑访问限制),而常对象只能调用类的 const 成员。

  定义常对象的语法格式:

	============== 常对象的定义 ==============
	// 写法 1
	const className objName(paramaters);		// 我们通常采用 const 置前的写法
	// 写法 2
	className const objName(paramaters);
	
	============ 常对象指针的定义 ============
	// 写法 1
	const className *objName = new className(paramaters);		// 我们通常采用 const 置前的写法
	// 写法 2
	className const *objName = new className(paramaters);
	

  常对象的定义示例:

	/* 定义 常对象 */
	const Student stu("张三", 16, 82.5f);
	/* 定义 常对象指针 */
	const Student *pstu = new Student("李四", 17, 84.5f);
  • 常对象的唯一特性:常对象只能调用类的 const 成员



补充要点 1. 类中还可以定义 typdef

  类中不仅可以定义变量和函数,还可以定义 typdef,类中定义的 typdef 使用时也需要作作用域指明,这说明了类也是一种作用域。

================================ 定义示例 ================================
/* 定义一个简单的类 */
class Demo {
	public:
		typedef const char * name_str;

	private:
		name_str m_name;

	public:
		Demo (name_str name) : m_name(name) {
			printf("-> 已添加 demo : %s \n", name);
		}

		void say (void) {
			printf("-> 这是 : %s \n", m_name);
		}
};
================================ 程序示例 ================================
int main () 
{
	Demo d("方块");
	d.say();
	Demo::name_str name = "球体";
	printf("-> 这是 : %s \n", name);
	
	return 0;
}
================================ 运行结果 ================================
-> 已添加 demo : 方块
-> 这是 : 方块
-> 这是 : 球体

2. 类的预先声明使用

  一般情况下,类必须在正式声明之后才能使用,比如:创建对象需要完整的类声明后才能进行,否则会引起编译器报错,因为创建对象时要为对象分配内存,而在正式声明类之前,编译器无法确定应该为对象分配多大的内存。
  但是某些情况下可以预先声明来进行使用,比如:预先声明一个类,然后在函数中添加这个类作为参数。这时所需要的仅仅是让编译器知道有这么一个类存在,而不需要其他任何信息,所以可以使用不完整的预先声明。也因为编译器必须要知道有这么个类存在,才能将其添加到函数的参数列表中,此时的预先声明就是必要的。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/866810.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-12
下一篇 2022-05-12

发表评论

登录后才能评论

评论列表(0条)

保存