C++进阶—第六章 类的封装性

本文介绍了C++中类的封装性,包括初始类、访问控制、this指针的使用以及代码的拆分。通过示例详细阐述了如何利用封装隐藏数据,以及如何通过构造函数和成员函数管理对象状态。同时,讲解了如何利用默认参数和默认数据成员初始化构造函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

6.类的封装性

6.1初始类

计算机语言中有2个重要概念:数据、算法。过程化编程强调算法,对象化编程强调数据。同类型数据存放在数组中,不同类型数据存放在结构体中,若要把不同类型的数据和对数据的处理算法放在一起,这就引申出了类。

在类中不仅可以存放不同类型的数据,还可以存放函数,这样就把数据和算法统一起来管理。类中的数据称为数据成员,类中的函数称为成员函数。类和结构体一样,其结尾需要使用分号来结束定义。为了帮助识别类,程序员在类的书写上都遵循一个约定,即类名第一个字母大写(也可以不大写)。

类的3大特性:封装性、继承性、抽象性,这一节主要讲封装性。

#include <iostream>

using namespace std;

 

class Student {   

public:

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

void printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

};  //需要使用分号结尾

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.name = "zhangsan";

stu1.age = 13;

stu1.sch = Student::school::primary_school;

stu1.curriculum = "English";

stu1.achievement = 80;

stu1.printinfo();

 

return 0;

}

运行结果:

学生姓名:zhangsan

年龄:13

学校:primary_school

课程:English

成绩:80

代码中定义了新的数据类型:Student类,在main()函数中,实例化对象是stu1(stu1称为对象,在结构体中是变量),数据成员:name、age、sch、curriculum、achievement;成员函数:void printinfo(void)。在Student类中,数据成员和成员函数的访问权限都是public,若不写public,则类默认访问权限是private。

6.2类的访问控制

对类中的数据和函数(算法)进行访问,需要对其访问权限进行控制,这就是类的访问控制。在类中,对象的访问有3种权限:public、protected、private。

public表示类的公有成员,类中定义的函数可以访问,类外定义的函数也可以访问。protected表示类的保护成员,类中定义的函数可以访问,派生类中定义的函数可以访问,类外的函数不可以访问。private表示类的私有成员,类中定义的函数可以访问,派生类和类外定义的函数不可以访问。派生类会在类的继承性那一章进行解释,这一章主要介绍public和private。

在代码中,通常把数据放在private中,将函数放在public中,这样将数据进行隐藏,使用者只需要看到成员函数即可(接口)。将数据存放在类私有成员中进行隐藏,这就是类的封装性。

#include <iostream>

using namespace std;

 

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

 

class Student {   

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

void init_student(const char * stu_name, int stu_age, school stu_sch, const char * stu_curriculum, int stu_achievement)

{

name = stu_name;

age = stu_age;

sch = stu_sch;

curriculum = stu_curriculum;

achievement = stu_achievement;

}

void printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

};  //需要使用分号结尾

 

int main(int argc, char **argv) {

 

Student stu1;

//stu1.name = "zhangsan";

//stu1.age = 13;

//stu1.sch = Student::school::primary_school;

//stu1.curriculum = "English";

//stu1.achievement = 80;

//stu1.printinfo();

stu1.init_student("zhansgan", 13, school::primary_school, "English", 80);

stu1.printinfo();

return 0;

}

这段代码是对上一段代码的优化,其运行结果相同。由于类中数据成员在main()中无法直接访问,因此在类中定义一个初始化函数init_student(),对类对象的数据成员进行初始化。

6.3 this指针

在前面的init_student()函数中,传入形参stu_name、stu_age、stu_sch、stu_curriculum、stu_achievement,为了让使用者更加明白形参的意思,能否直接用name、age、sch、curriculum、achievement来做为形参呢?可以的,为了区分形参中的name和类Student中数据成员name,C++引入了this指针。this->name表示类Student中数据成员name,这样2个name就区分开了。

实际上,this是一个指向对象本身的一个指针,从成员函数开始前构造,在成员函数结束时销毁;并且this指针只能在类的成员函数中使用。this指针还常用于在类的非静态成员函数中返回类对象本身的时候,直接使用return *this。

#include <iostream>

using namespace std;

 

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

 

class Student {   

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

void init_student(const char * name, int age, school sch, const char * curriculum, int achievement)

{

this->name = name;

this->age = age;

this->sch = sch;

this->curriculum = curriculum;

this->achievement = achievement;

}

Student change_name(const char * name)

{

this->name = name;

return *this;

}

void printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

};  //需要使用分号结尾

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.init_student("zhansgan", 13, school::primary_school, "English", 80);

stu1.change_name("lisi").printinfo();

 

return 0;

}

运行结果:

学生姓名:lisi

年龄:13

学校:primary_school

课程:English

成绩:80

由于stu1.change_name("lisi")返回类对象本身,因此可以继续调用类的成员函数。

6.4代码的拆分

若在类定义中,直接将函数写入,则这些函数都是内联函数。由于内联函数太消耗内存,我们可以把函数体写在类定义的外面,其函数实现需要加上类名和作用域运算符。若要定义为内联函数,则在函数定义的地方加inline即可。

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

 

class Student {   

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

void init_student(const char * name, int age, school sch, const char * curriculum, int achievement);

Student change_name(const char * name);

void printinfo(void);

 

};  //需要使用分号结尾

 

void Student::init_student(const char * name, int age, school sch, const char * curriculum, int achievement)

{

this->name = name;

this->age = age;

this->sch = sch;

this->curriculum = curriculum;

this->achievement = achievement;

}

inline Student Student::change_name(const char * name)

{

this->name = name;

return *this;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.init_student("zhansgan", 13, school::primary_school, "English", 80);

stu1.change_name("lisi").printinfo();

 

return 0;

}

当类的定义很庞大时,我们需要将类和main()分开。将类定义放在头文件中,将类成员函数的实现放在cpp文件中,在main()中包含头文件即可。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;             //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

void init_student(const char * name, int age, school sch, const char * curriculum, int achievement);

Student change_name(const char * name);

void printinfo(void);

 

};  //需要使用分号结尾

//student.cpp

#include "student.h"

 

void Student::init_student(const char * name, int age, school sch, const char * curriculum, int achievement)

{

this->name = name;

this->age = age;

this->sch = sch;

this->curriculum = curriculum;

this->achievement = achievement;

}

//不能写成inline Student Student::change_name(const char * name)

Student Student::change_name(const char * name)

{

this->name = name;

return *this;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

//main.cpp

#include "student.h"

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.init_student("zhansgan", 13, school::primary_school, "English", 80);

stu1.change_name("lisi").printinfo();

 

return 0;

}

运行结果:

学生姓名:lisi

年龄:13

学校:primary_school

课程:English

成绩:80

在上述代码中,使用了3个文件:student.h、student.cpp、main.cpp。student.h包含了类的定义,在student.cpp包含了类的实现(主要是成员函数的实现),main.cpp包含了类接口的使用。在student.cpp中需要包含student.h头文件;在main.cpp也要包含student.h头文件,由于函数是编译器搜索当前项目下的所有文件,因此不需要包含student.cpp(cpp文件无法包含)。由于内联函数不能跨文件使用,因此在student.cpp中不能使用内联函数,可以在student.h头文件的类定义中写入函数体。

6.5构造函数

在前面的代码中,我们每一次初始化对象都需要调用init_student()函数,有没有一种方法,可以在对象创建时,直接初始化对象?

有的,在C++中提供一种函数,名为构造函数,它在对象创建时自动被调用。由于构造函数在构造出对象之前,对象是不存在的,因此构造函数无法被对象来主动调用。

构造函数无返回值,其函数名与类名相同。当类中没有定义构造函数时,系统会提供一个无参默认构造函数(如前面代码中的Student stu1;);当类中定义了一个构造函数后,系统则不再提供无参默认构造函数。

当代码中定义了一个构造函数后,我们需要提供一个默认构造函数。默认构造函数分为2种,分别是无参默认构造函数和有参默认构造函数。2种默认构造函数在类中只能存在一个,不然会编译失败。

知识点1:无参默认构造函数

无参构造函数就是下面代码中的Student(),当main()中执行Student stu1会调用它。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char * name, int age, school sch, const char * curriculum, int achievement);

void printinfo(void);

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student()

{

cout << "Student()" << endl;

this->name = "no name";

this->age = 0;

this->sch = school::primary_school;

this->curriculum = "no curriculum";

this->achievement = 0;

}

 

Student::Student(const char * name, int age, school sch, const char * curriculum, int achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = name;

this->age = age;

this->sch = sch;

this->curriculum = curriculum;

this->achievement = achievement;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.printinfo();

Student stu2("zhansgan", 13, school::primary_school, "English", 80);

stu2.printinfo();

 

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhansgan

年龄:13

学校:primary_school

课程:English

成绩:80

代码解释:构造函数Student()Student(const char * name, int age, school sch, const char * curriculum, int achievement)分别是无参默认构造函数和有参构造函数,其调用顺序和对象创建的顺序一致。

知识点2:有参默认构造函数

有参默认构造函数就是下面代码中的Student(const char* name = "no name", int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 10)。(1)有参默认构造函数通过默认形参和函数内的运算来初始化数据成员,注意这种设置默认形参的方法只能运用于构造函数,其他函数不能使用。(2)有参默认构造函数中,函数的形参全部都要设置一个默认形参,若有一个没有设置,则不是有参默认构造函数。(3)设置默认形参的函数头只能存放在类定义的头文件中,不能存放在函数实现的cpp文件中。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student(const char* name = "no name", int age = 0, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0); //只能放在这里

void printinfo(void);

 

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = name;

this->age += age;

this->sch = sch;

this->curriculum = curriculum;

this->achievement = achievement;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

int main(int argc, char **argv) {

Student stu1;

stu1.printinfo();

Student stu2("zhangsan",12,school::primary_school,"English",80);

stu2.printinfo();

Student stu3("lisi", 13);

stu3.printinfo();

 

return 0;

}

运行结果:

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:English

成绩:80

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:lisi

年龄:13

学校:primary_school

课程:no curriculum

成绩:0

代码解释:由于有了一个有参默认构造函数,那原来的Student()就不能再使用。main()函数中Student stu1、Student stu2("zhangsan",12,school::primary_school,"English",80)、Student stu3("lisi", 13)都是调用有参默认构造函数Student(const char* name = "no name", int age = 0, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0)。这样,以后若有带不同个数形参的对象需要实例化,只需要调用有参默认构造函数就可以了,其未带参数按照顺序依次设置为构造函数中的默认形参。

其中,Student stu3("lisi", 13)实例化的stu3对象,其参数必须按顺序从第一个开始写,不能跨过前面写后面的参数;若中间有一个参数没有写,则后面的参数都不能写。

知识点3:带默认形参的构造函数

若我们想要Student()无参构造函数,也想要Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement)带默认形参的构造函数,怎么办?有方法的,当有参构造函数中,没有带全所有的默认形参,它就不被认为是默认构造函数。

在有参构造函数中,由于函数参数匹配是根据形参的顺序来的,因此若设置了第n个参数的默认形参,则第n参数后面的形参都要设置默认形参。

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

void printinfo(void);

 

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student()

{

cout << "Student()" << endl;

this->name = "no name";

this->age = 0;

this->sch = school::primary_school;

this->curriculum = "no curriculum";

this->achievement = 0;

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

this->name = name;

this->age += age;

this->sch = sch;

this->curriculum = curriculum;

this->achievement = achievement;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.printinfo();

Student stu2("zhangsan",12);

stu2.printinfo();

 

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:no curriculum

成绩:0

代码解释:Student stu1调用的是无参默认构造函数Student();Student stu2("zhangsan",12)调用的是带默认形参的有参构造函数Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0),其未带的参数按照构造函数的默认形参来处理。这种带默认形参的方法,只能用于类的构造函数。

知识点4:带默认数据成员的构造函数

既然构造函数的形参可以设置成默认的,那数据成员呢?在构造函数中,可以通过冒号在函数头处设置类的默认数据成员。

默认数据成员不能设置在类定义的构造函数声明中,而是设置在cpp文件的构造函数定义中。默认数据成员不需要使用this指针来区分是形参还是数据成员,只要写在冒号后的都是数据成员,数据成员的()内存放的是其初始化的数值,如name(name)中前一个数据成员,括号中的是形参

//student.h

#include <iostream>

using namespace std;

enum class school { primary_school, middle_school, collge };  //小学、中学、大学

class Student {

private:

const char* name;        //姓名

int age;          //年龄

school sch;              //学校

const char* curriculum;  //课程

int achievement;         //成绩

 

public:

Student();

Student(const char* name, int age = 10, school sch = school::primary_school, const char* curriculum = "no curriculum", int achievement = 0);

void printinfo(void);

 

};  //需要使用分号结尾

 

//student.cpp

#include "student.h"

 

Student::Student():name("no name"),age(0),sch(school::primary_school),curriculum("no curriculum"),achievement(0)

{

cout << "Student()" << endl;

}

Student::Student(const char* name, int age, school sch, const char* curriculum, int achievement) : name(name), age(age), sch(sch), curriculum(curriculum), achievement(achievement)

{

cout << "Student(const char * name, int age, school sch, const char * curriculum, int achievement)" << endl;

}

void Student::printinfo(void)

{

cout << "学生姓名:" << name << endl;

cout << "年龄:" << age << endl;

switch (int(sch))

{

case 0:

cout << "学校:" << "primary_school" << endl;

break;

case 1:

cout << "学校:" << "middle_school" << endl;

break;

case 2:

cout << "学校:" << "collge" << endl;

break;

default:

cout << "学校:" << "..." << endl;

}

cout << "课程:" << curriculum << endl;

cout << "成绩:" << achievement << endl;

}

 

//main.cpp

#include "student.h"

 

int main(int argc, char **argv) {

 

Student stu1;

stu1.printinfo();

Student stu2("zhangsan",12);

stu2.printinfo();

 

return 0;

}

运行结果:

Student()

学生姓名:no name

年龄:0

学校:primary_school

课程:no curriculum

成绩:0

Student(const char * name, int age, school sch, const char * curriculum, int achievement)

学生姓名:zhangsan

年龄:12

学校:primary_school

课程:no curriculum

成绩:0

代码解释:默认形参设置在头文件的构造函数声明中,默认数据成员设置在cpp文件的构造函数定义中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值