【C++ 程序设计】第 2 章:面向对象的基本概念

目录

一、结构化程序设计

二、面向对象程序设计的概念和特点

(1)面向对象程序设计的概念

(2)面向对象程序设计的特点 

三、类的初步知识

(1)类的定义

(2)类的定义示例

四、类的示例程序剖析

(1)程序结构

(2)成员变量与成员函数的定义

(3)创建类对象的基本形式

① 方法一

② 方法二

③ 方法三

五、访问对象的成员

(1)使用对象访问成员变量与调用成员函数 

(2)使用指针访问对象的成员 

(3)使用引用访问对象的成员 

六、类成员的可访问范围

(1)访问范围说明符的含义

(2)成员的访问

(3)隐藏的作用 

七、标识符的作用域与可见性

(1)函数原型作用域 

(2)局部作用域 

(3)类作用域  

(4)命名空间作用域 

(5)作用域隐藏规则




一、结构化程序设计

在结构化程序设计中,采用 自顶向下、逐步求精及模块化 的思想,将复杂的大问题层层分解为许多简单的小问题。
在编写程序时,使用 3 种基本控制结构来构造程序。
  • 可以说,程序基本上都含有顺序、选择、循环 3 种基本控制结构,这 3 种结构到目前为止仍是主要的控制结构。
  • 程序以控制结构为单位,只有一个入口和一个出口,基于控制结构可以从前往后地顺序阅读程序,程序的静态描述与执行时的控制流程容易对应,所以可以独立地理解各个部分。
  • 结构化程序设计主要强调的是程序的易读性。


二、面向对象程序设计的概念和特点

(1)面向对象程序设计的概念

所谓面向对象的程序设计方法,就是使分析、设计和实现一个系统的方法尽可能地接近人们认识一个系统的方法。通常包括 3 个方面:
  1. 面向对象的分析
  2. 面向对象的设计
  3. 面向对象的程序设计
面向对象技术把问题看成是相互作用的事物的集合,也就是对象的集合。
  • 对象具有两个特性:一是状态;二是行为。
  • 状态是指对象本身的信息,也称为属性
  • 行为是对对象的操作
  • 通过对事物的抽象找出同一类对象的共同属性(静态特征)和行为(动态特征),从而得到类的概念
  • 对象是类的一个具象,类是对象的一个抽象

(2)面向对象程序设计的特点 

C++ 中使用 对象名属性 和 操作 三要素来描述对象。

面向对象的程序设计有 抽象封装继承 和 多态 4 个基本特点:

1. 抽象:对象是系统中用来描述客观事物的一个实体,如各位员工是员工类的一个个对象。
  • 对象的特点包括两个方面:属性和操作
  • 属性指的是描述对象静态特征的数据项,如员工的姓名、职位、薪水等,可以用变量来表示
  • 操作指的是描述对象动态特征(即行为)的函数序列,也称为方法或服务,如员工可以请假、加班,员工还可以获得提拔、加薪等
2. 封装:在 C++ 中,通过用户定义的 来支持数据封装和信息隐藏。
3. 继承:在 C++ 现有类的基础上可以声明新的类,将一个已有类中的数据和函数保留,并加上自己特殊的数据和函数,从而构成一个新类,这就是继承和复用的思想。
  • 原来的类是基类,也称为父类超类
  • 新类是派生类,也称为子类
4. 多态:多态是指不同种类的对象都具有 名称相同 的行为,而具体行为的实现方式却有所不同。
  • 在一个类或多个类中,可以让多个方法使用同一个名字,从而具有多态性
  • 这是通过函数重载及运算符重载实现的多态



三、类的初步知识

C++的基本数据类型
名称
bool
布尔型
char
字符型
int
整型
float
浮点型
double
双精度浮点型

(1)类的定义

名称 代表 描述
成员变量 代表对象的“属性” 是类中的一类成员,个数不限,也称为数据成员。
成员变量的声明方式与普通变量的声明相同。
成员函数 代表对该类对象所含
数据进行操作的方法
是类中的另一类成员,个数不限,其声明方式与普通函数的声明相同。
类中的成员:
  • 按功能划分,包括 成员变量 和 成员函数
  • 按访问权限划分,包括 公有成员私有成员 和 保护成员
在 C++ 中还可以定义不是任何类的成员的函数,这样的函数可称为 “ 全局函数 ” 。
成员函数既可以在类体内定义,也可以在类体外定义。
  • 如果成员函数定义在类体内部,则默认是内联函数
  • 也可以在类体内部声明函数,并加上 inline 关键字,然后在类体外给出函数定义,这样的成员函数也是内联函数
标识符命名规则
  • 字母、数字和下划线的组合
  • 大小写敏感
  • 不能以数字开头
  • 不能和系统中使用的关键字完全相同
是具有 唯一标识符 的实体,就是说 类名不能重复
  • 类定义以 “;” 结束
  • 大括号中的部分称为类体
定义类时系统并不为类分配存储空间 ,而只是把类看作是一种模板或样板。
  • 或者说,类可以看作是用户自定义的一种数据类型
  • 在 C++ 98 标准下,类中声明的任何成员不能使用 auto、extern 和 register 关键字进行修饰
如果成员函数定义在类体外,则类体内必须要有函数原型, 类体外函数定义的前面必须用 “类名::” 来限定,格式如下:
返回值类型 类名::成员函数名(参数列表)
{
  成员函数的函数体
}
  • 类名是成员函数所属类的名字
  • 符号 :: 是类作用域运算符,表明它后面的成员函数是属于类名标识的这个类的。
  • 返回值类型就是这个成员函数返回值的类型
类 C 中不能定义类 C 的成员变量,但可以定义类 C 的指针和引用

(2)类的定义示例

#include<iostream>
#include<string>
using namespace std;

class myDate 
{
  public:
      myDate();                         //构造函数
      myDate(int, int, int);            //构造函数
      void setDate(int, int, int);      //设置日期
      void setDate(myDate);             //设置日期
      myDate getDate();                 //获取日期
      void setYear(int);                //设置年
      int getMonth();                   //获取月
      void printDate() const;           //打印日期
  private:
      int year, month, day;             //成员变量,表示年、月、日
}
// 在类体外定义成员函数
myDate::myDate()
{ 
  year=1970,month=1,day=1;
}

myDate::myDate(int y, int m, int d)
{ 
  year=y;month=m;day=d;
}

void myDate::setDate(int y, int m, int d) 
{ 
  year=y;month=m;day=d;
  return;
}

void myDate::setDate(myDate oneD)
{ 
  year = oneD.year; month = oneD.month; day = oneD.day;
  return;
}

myDate myDate::getDate()
{ 
  return *this;
}

void myDate::setYear(int y)
{
  year = y;
  return;
}

int myDate::getMonth()
{
  return month;
}

void myDate::printDate() const 
{
  cout<<year <<"/"<<month<<"/"<<day;
  return;
}
class Student 
{
  public:
      void setStudent(string, myDate);    //设置学生信息
      void setName(string);               //设置姓名
      string getName();                   //获取姓名
      void setBirthday(myDate);           //设置生日
      myDate getBirthday();               //获取生日
      void printStudent() const;          //打印信息
  private:
      string name;                        //姓名
      myDate birthday;                    //生日
};


// 在类体外定义成员函数
void Student::setStudent(string s, myDate d)
{ 
  name = s;
  birthday.setDate(d);
  return;
}

void Student::setName(string n)
{ 
  name = n; 
  return;
}

string Student::getName() 
{ 
  return name;
}

void Student::setBirthday(myDate d)
{ 
  birthday.setDate(d);
  return;
}

myDate Student::getBirthday() 
{ 
  return birthday;
}

void Student::printStudent() const 
{ 
  cout<<"姓名:"<<name<<"\t生日:";
  birthday.printDate();              //调用类myDate的成员函数
  cout<<endl;
}

int main( ) 
{ 
  Student ss; 
  int y,m,d; 
  string name_;
  cout<<"请输入学生的姓名和生日,生日以\"年月日\"的次序输入:";
  cin>>name_>>y>>m>>d;
  ss.setStudent(name_, myDate(y, m, d));
  ss.printStudent();
  return 0;
}


四、类的示例程序剖析

(1)程序结构

一个完整的 C++ 程序包括以下几部分:
  • —个主函数,可以调用其他函数,但不能被调用,也称为主程序
  • 用户定义的任意多个的类及全局函数
  • 全局说明:在所有函数和类定义之外的变量说明及函数原型
  • 注释
  • 头文件
对于比较大的程序,根据主函数和各用户定义的类及全局函数的功能及相互关系,可以
把类及全局函数划分为几个程序文件,包括 .cpp 文件和 .h 文件。
  • .cpp文件是源程序文件
  • .h文件是头文件
从逻辑关系上看,典型的 C++ 程序的结构包括类的定义、类中成员函数的实现及主函数
main。

(2)成员变量与成员函数的定义

实现成员函数时要指明类的名称,在类体外定义的一般格式如下:
返回值类型 类名::函数成员名(参数表)
{
       函数体
}
  • 成员函数并非每个对象各自存有一份。
  • 成员函数和普通函数一样,在内存中只有一份,它可以作用于不同的对象,为类中各对象共享
  • 通常,因为函数体代码较长,所以在类体内仅给出成员函数的原型,然后在类体外给出对应的函数体。
  • 如果函数体定义在类体内,则系统将其视为内联函数。
  • 类中定义的成员函数允许重载

(3)创建类对象的基本形式

① 方法一

【基本格式】类名 对象名;

【其他扩展单个对象写法】

  • 类名 对象名(参数);
  • 类名 对象名=类名(参数);
【其他扩展多个对象写法】
  • 类名 对象名1, 对象名2, …;
  • 类名 对象名1(参数1), 对象名2(参数2), …;

② 方法二

【基本格式】类名 *对象指针名 = new 类名;

【其他扩展写法】

  • 类名 *对象指针名 = new 类名();
  • 类名 *对象指针名 = new 类名(参数);
【说明】
  • 用 new 创建对象时返回的是一个对象指针,这个指针指向本类刚创建的这个对象
  • C++ 分配给指针的仅仅是存储指针值的空间,而对象所占用的空间分配在堆上
  • 使用 new 创建的对象,必须用  delete  来撤销

③ 方法三

与基本数据类型一样,还可以声明对象的引用、对象的指针及对象的数组。

【基本格式】

1. 声明对象引用,即变量别名的基本格式如下:
  • 类名 &对象引用名 = 对象;
2. 声明对象指针,即指向对象的指针的基本格式如下:
  • 类名 *对象指针名 = 对象的地址;
3. 声明对象数组的格式如下:
  • 类名 对象数组名[数组大小];

【说明】

  • 同类型的对象之间可以相互赋值
  • 对象和对象指针都可以用作函数参数
  • 函数的返回值可以是对象或指向对象的指针

【示例】定义了类 C 后,可以有如下的声明:

C a1,b1;      //定义了 C 类的对象 a1 和 b1
C *p = &a1;   //定义了指向对象 a1 的 C 类类型的指针 p
C &R = b1;    //定义了 C 类类型对象 b1 的引用 R
C A[3];       //定义了 C 类类型对象的数组 A,含 3 个元素


五、访问对象的成员

定义了类和对象后,就可以访问对象的成员。
【基本格式】

1. 通过对象访问成员变量的一般格式如下:

  • 对象名.成员变量名
2. 调用成员函数的一般格式如下:
  • 对象名.成员函数名(参数表)

(1)使用对象访问成员变量与调用成员函数 

int main( ) 
{ 
  Student ss; 
  int y,m,d; 
  string name_;
  cout<<"请输入学生的姓名和生日,生日以\"年月日\"的次序输入:";
  cin>>name_>>y>>m>>d;
  ss.setStudent(name_, myDate(y, m, d));
  ss.printStudent();
  return 0;
}


(2)使用指针访问对象的成员 

  1. 除了 对象名.成员名” 的格式外,还可以使用指针或引用的方式来访问类成员。
  2. 如果是通过指针访问成员变量,则点运算符 . 换为箭头运算符 -> ,即使用 指针->成员名” 的方式来访问对象的成员。
int main( ) 
{
  Student ss; 
  int y, m, d; 
  string name_;
  Student *sp = &ss;   //指向 ss 的指针 sp
  cout<<"请输入学生的姓名和生日,生日以\"年月日\"的次序输入:";
  cin >>name_>>y>>m>>d;
  sp->setStudent(name_,myDate(y,m,d));
  sp->printStudent( );
  return 0;
}


(3)使用引用访问对象的成员 

【示例】

int main( ) 
{
  Student ss; 
  int y,m,d; 
  string name_;
  Student &sy=ss;   //指向 ss 的指针 sp
  cout<<"请输入学生的姓名和生日,生日以\"年月日\"的次序输入:";
  cin>>name_>>y>>m>>d;
  sy.setStudent(name_,myDate(y,m,d));
  sy.printStudent( );
  return 0;
}

【分析】

  • 程序中定义了引用 sy 并进行初始化后,sy 与 ss 成为同一个对象的两个不同的名字。
  • 访问成员时仍使用点操作符,即 “引用名.成员名


六、类成员的可访问范围

(1)访问范围说明符的含义

访问范围说明符 含义 作用
public 公有的 使用它修饰的类的成员可以在程序的任何地方被访问
private 私有的 使用它修饰的类的成员仅能在本类内被访问
protected 保护的 它的作用介于public与private之间,使用它修饰的类的成员能在本类内及子类中被访问
私有类型的成员在类外是不能访问的,通过公有函数访问的效率比直接访问的效率要 低。
  • 为了权衡这两方面的因素,C++ 提供了友元访问方式。
  • 只有在类内和在友元函数内才可以访问私有成员。
类中定义的成员默认访问属性为  private 

(2)成员的访问

#include <iostream> 
using namespace std; 

class Box
{
  public: //公有的
      double length;
      void setWidth(double wid); 
      double getWidth();
  private: //私有的
      double width;
};


//类体外定义成员函数
double Box::getWidth()
{ 
  return width ;
}

void Box::setWidth(double wid)
{ 
  width=wid;
}

int main()
{ 
  Box box; //声明一个对象
  //不使用成员函数设置长度
  box.length=10.0; //正确,因为 length 是公有的
  cout<<"Length of box:"<<box.length<<endl; //输出Length of box:10
  //不使用成员函数设置宽度
  //box.width=10.0; //错误,因为width是私有的
  box.setWidth(10.0); //必须使用成员函数设置宽度
  cout<<"Width of box:"<<box.getWidth()<<endl; //输出Width of box:10
  return 0;
}

 

#include <iostream> 
#include <string> 
using namespace std; 

class CEmployee
{
  private:
      string szName; //姓名
      int salary; //工资
  public:
      void setName(string); //设置姓名
      string getName(); //获取姓名
      void setSalary(int); //设置工资
      int getSalary(); //获取工资
      int averageSalary(CEmployee); //计算两人的平均工资
};

void CEmployee::setName(string name)
{ 
  szName = name;
}

string CEmployee::getName()
{ 
  return szName;
}

void CEmployee::setSalary(int mon) 
{ 
  salary = mon;
}

int CEmployee::getSalary() 
{ 
  return salary;
}

int CEmployee::averageSalary(CEmployee e1) 
{ 
  return ( salary + e1 .getSalary() )/2;
}

int main()
{ 
  CEmployee eT, eY;
  //eT.szName ="Tom1234567"; 
  eT.setName("Tom1234567"); 
  //eT.salary=5000; //编译错误,不能直接访问私有成员
  eT.setSalary(5000); //需要通过公有成员函数访问
  cout<<eT.getName()<<endl; //输出 Tom1234567
  eY.setName("Yong7654321"); 
  eY.setSalary(3500);
  cout<<eY.getName()<<endl; //输出 Yong7654321
  cout<<"aver="<<eT.averageSalary(eY)<<endl; return 0;
}

 


(3)隐藏的作用 

  • 设置私有成员的机制叫作 “隐藏” 。
  • “隐藏” 的一个目的就是强制对私有成员变量的访问一定要通过公有成员函数进行。
  • 这样做的好处是:如果以后修改了成员变量的类型等属性,只需要更改成员函数即可。
  • 否则,所有直接访问成员变量的语句都需要修改。


七、标识符的作用域与可见性

标识符是组成程序的最小成分之一。
  • 类名、函数名、变量名、常量名和枚举类型的取值等都是标识符。
  • 这些标识符有各自的作用域和可见性。
  • 标识符的作用域是指标识符的有效范围,即它在程序中的存在区域。
  • 标识符的可见性是指在程序的哪个区域里可以使用。
  • 对于同一个标识符来说,这两个区域可能是不完全重合的。
C++ 中标识符的作用域有: 函数原型作用域、局部作用域(块作用域)、 类作用域和命名空间作用域。

(1)函数原型作用域 

在声明函数原型时形参的作用范围就是函数原型作用域,这是 C++ 程序中最小的作用域。例如,有如下的函数声明:
  • double area(double radius);
标识符 radius 的作用范围就在函数 area 形参列表的左右括号之间,在程序的其他地方不能引用这个标识符。
  • 因为函数声明中形参仅在形参列表中有效,所以,函数声明中往往不写形参名,而仅写形参的类型.作用域。

(2)局部作用域 


(3)类作用域  

类可以被看成是一组有名字的成员的集合,类 X 的成员 m 具有类作用域,对 m 的访问方式有如下 3 种:
  1. 如果在类 X 的成员函数中没有声明同名的局部作用域标识符,那么在该函数内可以直接访问成员 m 。也就是说,m 在这样的函数中起作用。
  2. 在类外,可以通过表达式 x.m 或者 X::m 来访问,其中 x 是类 X 的对象。这正是程序中访问对象成员的最基本方法。当然,这样的访问不能违反 m 的访问修饰符的限定。
  3. 在类外,可以通过 ptr->m 这样的表达式来访问,其中 ptr 为指向类 X 的一个对象的指针。当然,这样的访问不能违反 m 的访问修饰符的限定。

(4)命名空间作用域 

【格式】 

1. 定义命名空间的一般形式如下:
namespace 命名空间名
{
     命名空间内的各种声明(函数声明、类声明、…)
}
2. 在命名空间内部可以直接引用当前命名空间中声明的标识符,如果需要引用其他命名空间的标识符,需要使用下面的方式:
命名空间名::标识符名
3. 在标识符前面总要加上这样的命名空间限定会显得过于冗长,为了解决这一问题,C++
又提供了 using 语句,using 语句有两种形式:
  • using 命名空间名::标识符名;
  • using namespace命名空间名;
【示例】 
1.  定义一个命名空间如下:
namespace SomeNs
{
  class SomeClass{……};
  someFunc(int a){ }
};

2. 如果需要引用类名 SomeClass 或者函数名 someFunc(),需要使用下面的方式:

SomeNs::SomeClass obj1; //声明一个SomeNS::SomeClass型的对象obj1
SomeNs::someFunc(5); //调用someFunc()函数

(5)作用域隐藏规则

具有命名空间作用域的变量也称为全局变量 

对于在不同的作用域声明的标识符,可见性的一般原则如下:
  1. 标识符要声明在前,引用在后。
  2. 在同一个作用域中,不能声明同名的标识符。在没有互相包含关系的不同作用域中声明的同名标识符,互不影响。
  3. 如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层仍然可见。如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称内层标识符隐藏了外层同名标识符,这种机制称为隐藏规则。

【示例】

#include <iostream> 
using namespace std; 
int main()
{
  int a=1;
  cout<<a<<“\n”; //输出 1
  for(int i=1; i<2; i++)
     { 
       int a=2;
       cout<<a<<"\n"; //输出 2
     }
  cout<<a<<“\n”; //输出 1
  return 0;
}

【分析】

  • 主函数中有嵌套的两个块。
  • 外层块中定义了变量 ,赋初值 
  • 内层块(for 循环)中也定义了变量 a ,赋初值 
  • 这两个变量互相独立。
  • 在 for 循环中,外层定义的变量 将不可见。

猜你喜欢

转载自blog.csdn.net/qq_39720249/article/details/131016719