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文件的构造函数定义中。