C++(第三章——封装上)

在这里插入图片描述
" 总有一个地方的夕阳很美、我们一起去看看吧?"

这篇文章将带我们了解到(以后内容将头文件省略):

  1. C++中的类与对象
  2. C++中的数据成员和成员函数
  3. C++中的构造函数和析构函数
  4. C++中的对象复制和对象赋值

1、类与对象初体验

1.1、C++类与对象

  1. 类与对象的定义
  • 类是抽象的、而对象是具体的
  • 我们举一个例子、若我们将一个狗视作为一个对象、那么一群狗则可以称为一个(狗)类
class Dog
//关键字:class  类名:Dog
{
  char name[20];
  int age;
  int type;
//名字、年龄、种类称为类的数据成员(狗的属性)
  void speak();
  void run();
//speak() 和 run() 二个函数称为类的成员函数(狗的方法或行为)
};  
  1. 数据成员和成员函数
  • 若定义了一个类、类中的整型、字符型等变量称为类的数据成员
  • 若定义了一个类、类中的整型、字符型、空型等函数称为类的成员函数
  1. 一个问题
  • 类中的所列出的数据成员和成员函数是否是其全部的信息
  • 通俗点解释就是列出的名字、年龄或是speak()、run()函数是否就是狗这个类的全部信息
  1. 一个答案
  • 并非给出了所有信息、例如狗肉的蛋白质含量、狗的出栏日期等信息是没有显示出来的
  • 目的不同、抽象出的数据也不同
  • 我们应该思考哪些数据是我们想看到的、哪些又是我们不想看到的
  1. 二个名词解释
  • 选择性暴露:因为目的的不同而选择性的暴露出数据成员或是成员函数
  • 访问限定符:public(公共的)、protected(受保护的)、private(私有的)、此处不介绍protected(受保护的)
  1. 一个例子
class TV
//用class关键字定义TV(电视机)类
{
public:
//希望暴露
  char name[20];
  int type;
//数据成员包括其名字(name)、种类(type)
  
  void changevol();
  void power();
//成员函数包括其换台(changevol()函数)、电源(power()函数)
private:
//希望隐藏
  电阻调节
  像素配色
};

1.2、C++类对象的定义(实例化对象)

  1. 对象实例化(二种方式):根据一个类的设置制造出多个对象的过程
  • 从栈实例化(系统自动将内存回收)
class TV
{
public:
  char name[20];
  int type;
  
  void changevol();
  void power();
};

int main(void)
{
  TV tv;
//定义一个对象 格式:类名+对象名
  TV tv[20];
//定义多个(20)对象 格式:类名+对象名[i]
  return 0;
}
  • 从堆实例化(程序员需自行将内存回收)
class TV
{
public:
  char name[20];
  int type;
  
  void changevol();
  void power();
};

int main(void)
{
  TV *p = new TV();
//定义一个对象 格式:指针 = new 类名()
  TV *q = new TV[20];
//定义多个对象 格式:指针 = new 类名[i]
  //to do
  delete p;
//回收一个内存
  delete [ ]q;
//回收多个内存
  return 0;
}
  1. 对象成员的访问
  • 从栈中访问对象的元素
  • 通过TV tv的方式定义了一个对象tv
  • 通过tv.type访问对象中的数据成员
  • 通过tv.changeVol访问对象中的成员函数
class TV
{
public:
  char name[20];
  int type;
  
  void changevol();
  void power();
};

int main(void)
{
  TV tv;
  tv.type = 0;
  tv.changeVol(); 
  return 0;
}
  • 从堆中访问对象的元素
  • 通过TV *p = new TV()定义了一个对象 *p
  • 通过 p->type访问对象中的数据成员
  • 通过 p->changeVol()访问对象中的成员函数
class TV
{
public:
  char name[20];
  int type;
  
  void changevol();
  void power();
};

int main(void)
{
  TV *p = new TV();
  p->type = 0;
  p->changeVol();
  delete p;
  p = NULL;
  return 0;
}
  • 从堆中访问对象数组的元素
  • 通过TV *p = new TV[5]定义了一个对象数组
  • 通过 p[i]->type 访问每个对象中的数据成员
  • 通过 p[i]->changeVol()访问每个对象中的成员函数
class TV
{
public:
  char name[20];
  int type;
  
  void changevol();
  void power();
};

int main(void)
{
  TV *p = new TV[5];
  for(int i = 0;i < 5;i++)
  {
    p[i]->type = 0;
    p[i]->changeVol();
  }
  delete [ ]p;
  p = NULL;
  return 0;
}  

2、C++初始化String

2.1、关于string

字符串类型:string

  1. 初始化string对象的方式:
* string s1
* // s1为空串
* string s2("ABC");
* //用字符串字面值初始化s2
* string s3(s2)
* //将s3初始化为s2的一个副本
* string s4(n,'c')
* //将s4初始化为字符'c'的n个副本
  1. string常用操作
* s.empty()  
* //若s为空串、则返回true、否则返回false
* s.size()
* //返回s中的字符的个数
* s[n]
* //返回s中位置为n的字符、位置从0开始
* s1+s2
* //将二个串连接成新串、返回新生成的串
* s1 = s2
* //把s1的内容替换为s2的副本
* v1 == v2
* //判定相等、相等则返回true、否则返回false
* v1 != v2
* //判定不等,不等返回true、否则返回false
  1. 需要注意
  • 可以将string类型进行相加、但是不能将全是字符串类型的一组数据进行相加
* string s1 = "hello";
* string s2 ("world");
* string s3 = s1 + s2;
* string s4 = "hello" + s2;
* string s5 = "hello" + s2 + "world";
* string s6 = "hello" + "world";
* //错误 

2.2、string代码演示

要求:

  1. 提示用户输入姓名
  2. 接收用户的输入
  3. 然后向用户问好、hello xxx
  4. 告诉用户名字的长度
  5. 告诉用户名字的首字母是什么
  6. 如果用户直接输入回车、那么告诉用户的输入为空
  7. 如果用户输入的是imooc、那么告诉用户的角色是一个管理员
#include<string>
//要使用string类型必须调用头文件#include<string>
int main(void)
{
  string name;
  cout<< "Please input your name:";
  getline(cin,name);
//接收输入的名字、并传递给name
  if(name.empty())
  {
    cout<< "input is null.." <<endl;
    system("pause");
    return 0;
  }
  if(name == "imooc")
  {
    cout<< "you are a administrator" <<endl;
  }
  cout<< "hello" + name <<endl;
  cout<< "your name length:" + name.size() <<endl;
  cout<< "your name first letter is :" << name[0] <<endl;
  system("pause");
  return 0;
}

3、属性封装的艺术

3.1、C++属性封装之初始封装

面向对象的基本思想:以对象为中心、将所有的操作转化为成员函数的调用、也就是说对象在程序里的所有行为都通过调用自己的函数来完成

数据封装的好处:

  • 下列有三个代码块
  • 1是定义的一个关于学生的类、若我们直接实例化对象 Student stu(从栈中)、以 stu.age = 1000 的方式来进行赋值操作显然是不尽人意的
  • 2是我们进行的初步封装、开始以对象为中心、将所有的操作转化为成员函数的调用、也就是说、如果我们想要改变 age 的值、就必须调用其成员函数、我们定义一个类Student *p = new Student(从堆中)、以 p->setAge(1000) 的方式来对age进行赋值显然仍然也是不尽人意的
  • 3是我们做了进一步优化后的setAge()、一旦对传入的数据进行了大小判断、那么这样的赋值方式就极尽人意了
  1. 原始类
class Student
{
public:
  string name;
  int age;
  ......
};
  1. 对类的优化
class Student
{
public:
  void setAge(int _age)
  {
    age = _age;
  }
  int getAge()
  {
    return age;
  }
private:
  string name;
  int age;
  ......
};

  1. 对setAge()函数的优化
void setAge(int _age)
{
  if(_age > 0&&_age < 100)
  {
    age = _age;
  }
  else
  {
    ......
  }
}

3.2、只读属性

若定义了一个类,只能通过调用其函数来读取某个值、而不能改变这个值、我们称这位只读属性

class Car
{
public:
  int getWheelCount()
  {
    return m_iWheelCount;
  }
private:
  int m_iWheelCount;
};

3.3、C++属性封装代码演示

建议:将封装的数据成员、即private下面的数据

  1. 若是 string 型的 name 、则写为 m_strName
  2. 若是 int 型的age、则写为 m_iAge
  3. 相对应的set()函数里面的参数则写为_name和_age

要求:定义一个Student类、含有如下信息

  1. 姓名:name
  2. 性别:gender
  3. 学分(只读):score
  4. 学习:study
class Student
{
public:
  void initScore()
  {
    m_iScore = 0;
  }
  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 study(int _score)
  {
    m_iScore += -score;
  }
private:
  string m_strName;
  string m_strGender;
  int m_iscore;
//只读属性
}

int main(void)
{
  Student stu;
  stu.initScore();
//初始化学分为 0、不进行初始化是不能进行 stu.study() 调用的
  stu.setName("zhangsan");
  stu.setGender("女");
  stu.study(5);
  stu.study(3);
  cout<< stu.getName() << " " << stu.getGender() << " " <<stu.getScore() <<endl;
  system("pause");
  return 0;
}

4、精彩的类外定义

4.1、类内定义与内联函数

内联函数

  1. 关键字:inline
  2. 特点:编译时将函数体代码和实参代替函数调用语句
  3. 优点:效率更高、速度更快

类内定义

  1. 定义一个类、若成员函数的函数体在类里面定义、则称为类内定义
class Student
{
publicinline void setAge(int _age){age = _age}
//成员函数的函数体 {age = _age}
  inline int getAge(){return age;}
//成员函数的函数体 {return age;}
  inline void study(){//to do}
//成员函数的函数体 {//to do}
private:
  string name;
  int age;
};

4.2、类外定义

类外定义

  • 同文件类外定义
  1. 同文件类外定义(文件 Car.cpp )
  2. 成员函数的函数体写在类的外面
  3. 格式:类型+类名::函数名
class Car
{
public:
  void run();
  void stop();
  void changeSpeed();
};
void Car::run(){ }
void Car::stop(){ }
void Car::changeSpeed(){ }
  • 分文件内外定义
  1. 分文件内外定义(文件 Car.cpp 和 Car.h )
  2. 成员函数的函数体写在类和文件的外面
  3. 格式:类型+类名::函数名
  4. 头文件需要加上 #include " 文件名"

文件 Car.h

class Car
{
publicvoid run();
  void stop();
  void changeSpeed();
};

文件 Car.cpp

#includ "Car.h"
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}

5、对象的生离死别

5.1、C++构造函数

思考:

  1. 实例化的对象是如何在内存中存储的?
  2. 类中的代码又是如何存储的?
  3. 代码和数据又有怎样的关系?

5.1.1、内存分区

* 栈区:int x = 0; int *p = NULL;
* //内存的申请和回收都不需要程序员操心
* 堆区:int *p = new int[20];
* //内存的申请和回收都需要程序员操心
* 全局区:存储全局变量和静态变量
* //后续介绍
* 常量区:string str = "hello";
* 代码区:存储逻辑代码的二进制

5.1.2、对象初始化

  1. 有且只有一次的初始化
  2. 根据条件初始化(此处不进行讲解)
  • 坦克大战里面的坦克出现的位置都是固定的
  • 下列初始化过程将二个坦克的横纵坐标都置为0
class Tank
{
private:
  int m_iPosX;
//横坐标
  int m_iPosY;
//纵坐标
public:
  void init()
  {
     m_iPosX = 0;
     m_iPosY = 0;
   }
};

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

5.1.3、构造函数

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

  • 构造函数与类同名
  • 构造函数没有返回值
  • 构造函数可以有多个重载形式
  • 实例化对象时仅用到一个构造函数
  • 当用户没有定义构造函数时、编译器自动生成一个构造函数
  1. 无参构造函数
class Student
{
publicStudent()
  {
    m_strName = "jim";
  }
private;
  string m_strName;
};
  1. 有参构造函数
class Student
{
publicStudent(string name)
  {
    m_strName = name;
  }
private;
  string m_strName;
};
  1. 重载构造函数
class Student
{
publicStudent()
  {
    m_strName = "jim";
  }
   Student(string name)
  {
    m_strName = name;
  }
private;
  string m_strName;
};

5.2、构造函数初始化列表

默认构造函数:实例化对象时不需要传递参数的构造函数称为默认构造函数

初始化列表

  1. 初始化列表先于构造函数执行
  2. 初始化列表只能用于构造函数
  3. 初始化列表可以同时初始化多个数据成员
  4. 效率高、速度快(建议使用)
class Student
{
publicStudent():m_strName("jim"),m_iAge(10){}
//初始化列表
private:
  string m_strName;
  int m_iAge;
};

初始化列表存在的必要性

  • 错误程序如下
  • 因为m_dPi是const修饰的常量、不能再给它赋值
class Circle
{
publicCircle()
  {
    m_dPi = 3.14
  }
private:
  const double m_dPi;
};
  • 正确程序如下
  • 利用初始化列表的方式将常量赋值是被予许的
class Circle
{
publicCircle():m_dPi(3.14){}
private:
  const double m_dPi;
};

5.2.1、初始化列表编码

  1. 文件 “Teacher.h”
#include <iostream>
#include <string>
//引用string类型必须添加的头文件
using namespace std;
class Teacher
{
public:
  Teacher(string name = "jim",int age = 1,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;
//const修饰 m_iMax、它的值只能由初始化列表来赋予
};
  1. 文件 “Teacher.cpp”
  • 初始化列表:Teacher::Teacher(string name,int age,int m):m_strName(name),m_iAge(age),m_iMax(m)
  • 分别将冒号后面括号里面的值赋予前面的变量
#include "Teacher.h"

Teacher::Teacher(string name,int age,int m):m_strName(name),m_iAge(age),m_iMax(m)
//初始化列表
{
  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::getMax()
{
  return m_getMax;
}
  1. 文件 “demo.cpp”
  • t1 和 t2 的区别是一个在定义对象的时候进行了初始化、一个没有进行初始化
int main(void)
{
  Teacher t1("Merry",12);
  Teacher t2;
  cout<< t1.getName() << " " << t1.getAge() << " " << t1.getMax() <<endl;
  cout<< t2.getName() << " " << t2.getAge() << " " << t2.getMax() <<endl;
  
  system("pause");
  return 0;
}

5.3、拷贝构造函数

  1. 下列程序从理论上应该调用三次构造函数、可事实不是如此
class Student
{
public:
  Student()
  {
    cout<< "Student";
  }
private:
  string m_strName;
};

int main(void)
{
  Student stu1;
  Student stu2 = stu1;
  Student stu3(stu1);
  
  return 0;
}
  1. 拷贝构造函数定义格式:类名(const 类名&变量名)
  • 如果没有自定义的拷贝构造函数则系统自动生成一个默认的拷构造函数
  • 当采用直接初始化(stu1(stu2))或复制初始化(stu1 = stu3)实例化对象时系统自动调用拷贝构造函数
class Student
{
public:
  Student()
  {
    m_strName = "jim";
  }
  Student(const  Student& stu){}
private:
  string m_strName;
};

5.4、析构函数

  • 如果构造函数是对象来到世间的第一声哭泣
  • 那么析构函数就是对象临终的遗言

定义格式:~类名()
析构函数是不允许加参数的

class Student
{
public:
  Student(){cout<< "Student" <<endl;}
  ~ Student(){cout << "~Student" <<endl;}
private:
  string m_strName;
};
  1. 析构函数存在的必要性
  • 如果没有自定义的析构函数则系统自动生成
  • 析构函数在对象销毁时自动调用
  • 析构函数没有返回值、没有参数也不能重载
class Student
{
public:
  Student(){m_pName = new char[20];}
  ~Student(){delete [ ]m_pName;}
private:
  char *m_pName;
};

class Student
{
public:
  Student(){m_pName = new char;}
  ~Student(){delete m_pName;}
private:
  char m_pName;
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是我来晚了!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值