C++面向对象之封装篇(上)

Posted wx62cea850b9e28

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++面向对象之封装篇(上)相关的知识,希望对你有一定的参考价值。


学习总结

(1)构造函数可以有多个重载形式;在实例化对象时,即使有多个构造函数,也仅用到其中的一个构造函数;当用户没有定义构造函数时,编译器将自动生成一个构造函数。
(2)拷贝构造函数:定义格式:类名(​​​const​​​ 类名 & 变量名)
首先要加一个​​​const​​​关键字,其次传入的是一个引用,这个引用还是一个与自己的数据类型完全相同(比如,一个​​Student​​​的一个对象)的引用。
(3)初始化列表先于构造函数执行;只能用于构造函数;可同时初始化多个数据成员。
(4)构造函数的分类:
按照参数分类:有参和无参(默认构造函数)构造;
按照类型分类:普通构造和拷贝构造。

文章目录

一、封装篇(上)

1.1 类和对象、类对象定义

3.1.1 引例----狗

class Dog//class为关键字
char name[20];
int Age;//属性
int type;//数据成员

void spead();//方法
void run();//成员函数

3.1.2 引例----电视机

class TV
public:
char name[20];//电视名字
int type;//电视类型

void changeVol();//调节音量
void power();//开关电源
;

3.1.3 对象的实例化
(1)从栈实例化
(使用完后,系统会自动将其所占内存释放)

int main()
TV tv;//定义一个对象
TV tv[10];//定义一个对象数组
return 0;

(2)从堆实例化
(使用完后,要我们自己释放内存)

int main()
TV *p=new TV();//定义一个对象
TV *q=new TV[10];//定义一个对象数组
//对象使用完后,需要释放内存
delete p;
delete []q;//删除指向对象数组的指针
return 0;

3.1.4 对象成员的访问

通过不同实例化对象的方法,生成的对象访问其数据成员和成员函数的方式也不同

(1)栈实例化对象访问其成员

(2)堆实例化单一对象访问其成员

(3)堆实例化对象数组访问其成员

【C++】面向对象之封装篇(上)_c++


3.1.5 代码实践

#include<iostream>
#include<stdlib.h>
using namespace std;
class Coordinate
public:
int x;
int y;
void printX()
cout<<x<<endl;

void printY()
cout<<y<<endl;

;
int main()
Coordinate coor;//从栈中实例化一个坐标对象
coor.x=10;
coor.y=20;
coor.printX();
coor.printY();

Coordinate *p=new Coordinate();//从堆中实例化一个坐标对象
//这里需要判断申请的内存是否成功
if(NULL==p)
cout<<"申请内存失败"<<endl;
return 0;

p->x=100;
p->y=200;
p->printX();
p->printY();
//对象使用完后,需要释放内存
delete p;
p=NULL;

system("pause");
return 0;

输出结果为:

【C++】面向对象之封装篇(上)_后端_02

1.2 初始字符串类型

(1)string类型

字符串,如果只用char数组,经常使用strlen、strcat、strcmp函数会比较麻烦,而C++就因此引入​​string​​类型。

string hobby="football";
cout<<hobby<<endl;

(2)初始化string对象的方式

string s1;//s1为空字符串
string s2("ABC");//用字符串字面值初始化s2
string s3(s2);//将s3初始化为s2的一个副本
string s4(n,c);//将s4初始化为字符‘c’的n个副本

(3)string的常用操作

s.empty() //若s为空串,则返回true
s.size()//返回s中字符的个数
s[n]//返回s中位置为n的字符,位置从0开始
s1+s2//将两个串连接成新串,返回新生成的串
s1=s2//将s1的内容替换为s2的副本

(4)代码实践

题目描述:
1 提示用户输入姓名
2 接收用户的输入
3 然后向用户问好,hello xxxx
4 告诉用户名字的长度
5 告诉用户名字的首字母是什么
6 如果用户直接输入回车,那么告诉用户输入的为空
7 如果用户输入的是imooc,那么告诉用户的角色是一个管理员

#include<iostream>
#include<stdlib.h>
#include<string>
using namespace std;
int main()

string name;
cout<<"please input your name: ";
getline(cin, name);
if(name.empty())

cout<<"input is null..."<<endl;
system("pause");
return 0;

if(name == "imooc")

cout<<"You are a administartor"<<endl;

cout<<"hello " + name <<endl;
cout<<"Your names length is:"<<name.size() <<endl;
cout<<"Your names first letter is: "<< name[0] <<endl;

system("pause");
return 0;

1.3 属性封装的艺术

(1)数据的封装

如果按照C语言的面向过程的习惯,我们会这样写:

class Student
public:
string name;
int age;
;
int main()
Student stu;
stu.name=Jim;
stu.age=10;
return 0;

而面向对象:以对象为中心,要以谁做什么来表达程序的逻辑,
代码层上:将所有的数据操作转换为成员函数的调用,对象在程序中所有的行为都通过自己的函数完成

通过函数来封装数据成员:

class Student
private:
void setAge(int _age)
age=_age;

int getAge()
return age;

private:
string name;
int age;
;

即上面的2个成员函数:一个设置年龄的值,一个读取年龄的值。

(2)封装的好处

(1)防止非法输入
如上面的年龄防止输入1000这种不合理的数据。

class Student
private:
void setAge(int _age)
if(_age>0 && _age<100)
age=_age;
else
.....


int getAge()
return age;

private:
string name;
int age;
;

(2)可以控制外界对数据的访问属性
如​​​Car​​​类,我们不希望外界通过某个函数改变​​m_iWheelCount​​​变量的值,即只希望设置为只读属性,那就设置为​​private​​。

class Car
public:
int getWheelCount()
return m_iWheelCount;

private:
int m_iWheelCount;
;

(3)代码实践

定义一个Student类,含有如下信息:
1 姓名:name
2 性别:gender
3 学分(只读):score
4 学习:study(用于获得学分)

#include<iostream>
#include<stdlib.h>
#include<string>
using namespace std;
/* **********数据的封装
定义一个Student类,含有如下信息:
姓名:name
性别:gender
学分(只读):score
学习:study(用于获得学分)
/* ************************************/
class Student
public:
void setName(string _name)
m_strName=_name;

string getName()
return m_strName;

void setGender(string _gender)
m_strGender = _gender;

string getGender()
return m_strGender;

int getScore()
return m_iScore;

void initScore()
m_iScore=0;

void study(int _score)
m_iScore += _score;

private:
string m_strName;
string m_strGender;
int m_iScore;
;
int main()
Student stu;
stu.initScore();//如果这里不进行初始化,m_iScore的值就不可控
stu.setName("Zhangsan");
stu.setGender("女");
stu.study(5);//学习一门学分为5分的课程
stu.study(3);//学习一门学费为3分的课程

cout<<stu.getName()<<" "<<stu.getGender()<<" "<<stu.getScore()<<endl;
system("pause");
return 0;

【C++】面向对象之封装篇(上)_面向对象_03

1.4 类内定义与类外定义

(1)类内定义

将成员函数的函数体写在类的内部。

类内定义与内联函数的关系

类内定义的成员函数,编译器会将其优先编译为内联函数
但对于复杂的成员函数无法编译成内联函数的,就编译成普通的函数。

普通函数和内联函数的区别:

【C++】面向对象之封装篇(上)_构造函数_04


上面的步骤即体现了内联函数是省掉了(2)和(4)步骤,可以为调用节省很多时间(尤其对于循环调用时省时)。

(2)类外定义

即将成员函数的函数体写在类的外面。
分为:同文件类外定义、分文件类外定义

1.同文件类外定义

注意开头写上类名和::

【C++】面向对象之封装篇(上)_构造函数_05

2.分文件类外定义(重点)

几乎所有的C++项目都会将类的定义分文件完成。
头文件,类名建议和文件名写成一样。

class Car
public:
void run();
void stop();
void changeSpeed();
;

在另一个文件的开头记得将头文件​​Car.h​​写上:

#include "Car.h"
void Car::run()
void Car::stop()
void Car::changeSpeed()

1.5 对象的构造

学习前的思考:
(1)实例化的对象是如何在内存中存储的?
(2)类中的代码又是如何存储的?
(3)数据和代码之间的关系是咋样的?

(1)对象结构

内存中按照用途被划分的5个区域

int x=0;int *p=NULL;//存储在栈
int *p=new int[20];//存储在堆区,注意这里的数组名为p,而不是int(关键字int)
存储全局变量和静态变量//全局区
string str="hello";//常量区
存储逻辑代码的二进制//代码区

栈区:内存由系统进行控制(程序员不需要关心其分配和回收)

堆区:​​new​​​分配一段内存(会分配在堆区),需要我们自己用​​delete​​回收这段内存。

全局区:全局变量、静态变量

常量区:字符串、常量

代码区:存储编译后的二进制代码


定义一个Car类,在类被实例化前是不会用到堆or栈中的内存的,

class Car
private:
int wheelCount;
public:
int getWheelCout()
return wheelCount;

【C++】面向对象之封装篇(上)_面向对象_06


但当它实例化后,如实例化出car1、car2、car3对象,每个对象都存储会在【栈】中(不同对象在栈中位置不同),但是逻辑代码却只编译出一份(放在代码区)

当需要的时候,这个代码区中的代码供所有的对象使用。

(2)对象初始化

上面的实例化三个对象后,每个对象中的数据都不可控(因为没有对数据初始化)。

栗子:
定义坦克类,只描述位置,实例化一个坦克对象t1后,通过t1调用初始化函数(init)。

class Tank
private:
int m_iPosX;
int m_iPosY;
public:
void init()
m_iPosX=0;
m_iPosY=0;

;
int main()
Tank t1;
t1.init();
Tank t2;
t2.init();
return 0;

对象的初始化:可能有且仅有一次 or 根据条件初始化多次。

(3)构造函数

【C++】面向对象之封装篇(上)_面向对象_07


有时需要有且仅有一次的初始化操作,为了防止0次或者多次调用初始化函数,C++推出构造函数。

构造函数:在对象实例化时被自动调用(仅被调用一次)。

相关规则:
(1)构造函数与类同名;
(2)没有返回值,连​​​void​​​都不用写;
(3)可以有多个重载形式;
(4)在实例化对象时,即使有多个构造函数,也仅用到其中的一个构造函数
(5)当用户没有定义构造函数时,编译器将自动生成一个构造函数。

1)无参构造函数

class Student
public:
Student()
m_strName="Keiven";

private:
string m_strName;
;

在Student类中,构造函数没有任何返回值,构造函数内部对数据成员进行赋值操作(初始化)。

2)有参构造函数

class Student
public:
Student(string name)
m_strName=name;

private:
string m_strName;
;

这种构造函数即在用户实例化一个Student对象时可以传进一个name作初始值。

(4)构造函数代码实践

先定义头文件,注意下面的类里的构造函数参数中age=40是在设置默认参数——如果没有传参年龄,即只传参了名字也会直接调用第二个构造函数,而年龄默认为40(设置默认参数)。

【C++】面向对象之封装篇(上)_c++_08


头文件:

#include<iostream>
#include<string>
using namespace std;
class Teacher
public:
Teacher();//申请无参构造函数
//Teacher(string name,int age);//申请有参构造函数
Teacher(string name,int age=40);
void setName(string name);
string getName();
void setAge(int age);
int getAge();
private:
string m_strName;
int m_iAge;
;

主函数如下:

#include"Teacher.h"
#include<stdlib.h>
#include<iostream>
#include<stdio.h>
using namespace std;
/* ************************************************************************/
/* 定义一个Teacher类,具体要求如下:
/* 自定义无参构造函数
/* 自定义有参构造函数
/* 数据成员:
/* 名字
/* 年龄
/* 成员函数:
/* 数据成员的封装函数
/* ************************************************************************/

//自定义无参构造函数
Teacher::Teacher()
m_strName="Keiven";
m_iAge=20;
cout<<"Teacher()"<<endl;

//自定义有参构造函数
Teacher::Teacher(string name,int age)
m_strName=name;
m_iAge=age;
cout<<"Teacher(string name,int age)"<<endl;

void Teacher::setName(string _name)
m_strName=_name;

string Teacher::getName()
return m_strName;

void Teacher::setAge(int _age)
m_iAge=_age;

int Teacher::getAge()
return m_iAge;

int main()
Teacher t1;//调用无参构造函数
Teacher t2("Mery",30);//调用有参构造函数
Teacher t3("James");

//检查上面3个构造函数是否有初始化数据成员
cout<<t1.getName()<<" "<<t1.getAge()<<endl;
cout<<t2.getName()<<" "<<t2.getAge()<<endl;
cout<<t3.getName()<<" "<<t3.getAge()<<endl;
system("pause");

输出结果为:

【C++】面向对象之封装篇(上)_后端_09


注意:

构造函数除了可以重载,还可以给参数赋值默认值,

但不能随意的赋值默认值,有时会引起编译时不通过——因为实例化对象时,编译器不知道调用哪一个构造函数。

(5)默认构造函数

默认构造函数:在实例化对象时,不需要传递参数的构造函数。
注意:调用默认构造函数时,不要加​​​()​​,否则编译器会认为是一个函数的声明,不会认为在创建对象。

class Student
public:
Student()
Student(string name="Keiven");
private:
string m_strName;
;
int main()
Student stu1;
Student *p=NULL;
p=new Student();

实例化2个对象:一个是stu1(从堆中实例化对象),
另一个是用p指针指向实例化的一个对象所在的栈空间(从堆中实例化一个对象)。

无论是从栈中还是堆中实例化对象,共同特点:调用的构造函数都不用传参——如上面代码有两种情况都不用传参:

Student()
Student(string name="Keiven");

(6)构造函数初始化列表

在这个例子中,我们定义了一个学生Student的类,在这个类中,我们定义了两个数据成员(一个名字,一个年龄),名字和年龄都通过初始化列表(即红色标记部分)进行了初始化。姓名和年龄都通过初始化列表进行初始化

注意
(1)构造函数后需要冒号隔开,多个数据成员之间则是用逗号隔开
(2)赋值用括号赋值,而非等号

class Student
public:
Student():m_strName("Keiven"),m_iAge(30)
private:
string m_strName;
int m_iAge;
;

1)特点

(1)初始化列表先于构造函数执行
(2)初始化列表只能用于构造函数
(3)初始化列表可同时初始化多个数据成员

2)必要性(牛逼处)

(1)会报错的栗子:

在这个例子中,我们定义了一个圆Circle的类,在这个类中定义了一个pi值,因为pi是不变的,我们用const来修饰,从而pi就变成了一个常量。我们如果用构造函数来初始化这个常量(像上面那样),这样的话编译器就会报错,而且会告诉我们,因为pi是常量,不能再给它进行赋值。也就是说,我们用构造函数对pi进行赋值,就相当于第二次给pi赋值了。那如果我们想给这个pi赋值,并且又不导致语法错误,怎么办呢?唯一的办法就是通过初始化列表来实现(如下)。这个时候,编译器就可以正常工作了。

class Circle
public:
Circle()m_dPi=3.14;
private:
const double m_dPi;
;

原因:​​const​​​修饰的​​m_dPi​​是常量,即编译器会因为不能给他进行赋值而报错(二次赋值),而唯一办法是通过初始化列表解决。

(2)不报错(解决)

class Circle
public:
Circle()m_dPi=3.14;
private:
const double m_dPi;
;

(7)初始化列表代码实践

描述:定义一个Teacher类,自定义有参默认构造函数,适用初始化列表初始化数据;数据成员包含姓名和年龄;成员函数为数据成员的封装函数;拓展:定义可以带最多学生的个数,此为常量。

【C++】面向对象之封装篇(上)_构造函数_10


​Teacher.h​​头文件:

#include<iostream>
#include<string>
using namespace std;
class Teacher
public:
Teacher(string name="Keiven",int age=20,int m=100);
//申明有参默认构造函数,带有初始值
void setName(string name);
string getName();
void setAge(int age);
int getAge();
int getMax();
private:
string m_strName;
int m_iAge;
const int m_iMax;//常量(只能通过初始化列表进行初始化
;

源文件:

#include"Teacher.h"
#include<iostream>
#include<stdlib.h>
using namespace std;
/* ************************************************************************/

/* 定义一个Teacher类,具体要求如下:
/* 自定义有参默认构造函数
/* 适用初始化列表初始化数据
/* 数据成员:
/* 名字
/* 年龄
/* 成员函数:
/* 数据成员的封装函数
/* 拓展:
/* 定义可以带最多学生的个数,此为常量
/* ************************************************************************/

Teacher::Teacher(string name,int age,int m):m_strName(name),m_iAge(age),m_iMax(m)
//初始化列表形式定义有参构造函数
cout<<"Teacher(string name,int age)"<<endl;


int Teacher::getMax()
return m_iMax;


void Teacher::setName(string _name)
m_strName=_name;


string Teacher::getName()
return m_strName;

void Teacher::setAge(int _age)
m_iAge=_age;

int Teacher::getAge()
return m_iAge;


int main()
Teacher t1("Mery",30,150);

//看2个构造函数是否完成数据成员的初始化
cout<<t1.getName()<<" "<<t1.getAge()<<" "<<t1.getMax()<<endl;

system("pause");

(8)拷贝构造函数

实例化三个对象,本以为会打印出3个​​student​​,而结果只打印了一个:

class Student
<

以上是关于C++面向对象之封装篇(上)的主要内容,如果未能解决你的问题,请参考以下文章

C++入门篇之类和对象上

C++对象之谜(封装篇)

C++之面向对象的三个基本特征

C++类和对象之封装

C++类和对象之封装

RK3568平台开发系列讲解(驱动基础篇)Linux内核面向对象思想之封装