第四章 面向对象程序设计的基本特点 课堂笔记

类和对象
 
 类和对象
• 类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
• 利用类可以实现数据的封装、隐藏、继承与派生。
• 利用类易于编写大型复杂程序,其模块化程度比C中采用函数更高。‘
 
• 新类型的对象该如何被创建和销毁?
  ▫ 构造和析构函数
  ▫ 内存分配和释放函数(operator new、operator new[]、operator delete、operator delete[]) 
 
*设计class就是设计类型(续)
• 对象的初始化和赋值有何差别?
  ▫ 复制构造
  ▫ 赋值 
• 对象作为函数的参数如何以值传递?
 
4.2.1 类的定义
类是一种用户自定义类型,声明形式:
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护型成员
}
4.2 类和对象  
4.2.2 类成员的访问控制——公有类型成员
• 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
   私有类型成员在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。
 
保护类型成员,与private类似,其差别表现在继承与派生时对派生类的影响不同,第七章讲。
 
对象
• 类的对象是该类的某一特定实体,即类类型的变量。
• 声明形式:
类名 对象名;
• 例:Clock myClock;
• 类中成员互访
  ▫ 直接使用成员名
• 类外访问
  ▫ 使用“对象名.成员名”方式访问 public 属性的成员
 
类和对象
类的成员函数
• 在类中说明原型,可以在类外给出函数体实现,并在函数名前使用类名加以限定。也可以直接在类中给出函数体,形成内联成员函数。
• 允许声明重载函数和带默认形参值的函数
 
内联成员函数
• 为了提高运行时的效率,对于较简单的函数可以
声明为内联形式。
• 内联函数体中不要有复杂结构(如循环语句和
switch语句)。
• 在类中声明内联成员函数的方式:
  ▫ 将函数体放在类的声明中。
  ▫ 使用inline关键字。
 
构造函数和析构函数
 
 构造函数
• 构造函数的作用是在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态         
• 在对象创建时被自动调用
• 如果程序中未声明,则系统自动产生出一个默认的构造函数,其参数列表为空
• 构造函数可以是内联函数、重载函数、带默认参数值的函数
 
默认构造函数
• 调用时可以不需要参数的构造函数都是默认构造函数。
▫ 当不定义构造函数时,编译器自动产生默认构造函数
▫ 在类中可以自定义无参数的构造函数,也是默认构造函数
▫ 全部参数都有默认形参值的构造函数也是默认构造函数
• 下面两个都是默认构造函数,如果在类中同时出现,将产生编译错误:
Clock();
Clock(int newH=0,int newM=0,int newS=0);
 
复制构造函数
复制构造函数是一种特殊的构造函数,其形参为本类的对
象引用。作用是用一个已存在的对象去初始化同类型的新对
象。
class 类名 {
public :
类名(形参);//构造函数
类名(const 类名 &对象名);//复制构造函数
...
};
类名::类( const 类名 &对象名)//复制构造函数的实现
{ 函数体 }
 
 
 
复制构造函数
• 复制构造函数被调用的三种情况
▫ 定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
▫ 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
▫ 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主
 
调函数,此时发生复制构造。
 
 
函数参数尽量传递常引用而不是值
• 传递对象值会引起复制构造和析构,增加时间空间开销。
• 传常引用可避免这一问题。以引用做参数时,尽量使用常引用。
 
隐含的复制构造函数
如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数。
这个构造函数执行的功能是:用作为初始值对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。
 
 
析构函数
• 完成对象被删除前的一些清理工作。
• 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。
• 如果程序中未声明析构函数,编译器将自动产生一个隐含的析构函数。
 
编译器默认提供的函数
class Empty{
public:
  Empty(){} //这里这个构造函数,应该是,不作任何初始化,类内变量值是随机的
  Empty(const Empty& rhs){…}
  ~Empty(){}
  Empty operator=(const Empty& rhs){…}
};
 
有时不应该进行复制和赋值,略》》》》》》
 
类的组合
 • 类中的成员数据是另一个类的对象。
• 可以在已有抽象的基础上实现更复杂的抽象。类组合的构造函数设计
• 原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。
• 声明形式:

类名::类名(对象成员所需的形参,本类成员形参) :对象1(参数),对象2(参数),......
{
  //函数体其他语句
}
构造组合类对象时的初始化次序
• 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序。
▫ 成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造。 ▫ 初始化列表中未出现的成员对象,调用用默认构造函数(即无形参的)初始化
• 处理完初始化列表之后,再执行构造函数的函数体。

 
 
UML图形标识
 
 
 
结构体和联合体
 
• 结构体是一种特殊形态的类
▫ 与类的唯一区别:类的缺省访问权限是private,结构体的缺省访问权限是public
▫ 结构体存在的主要原因:与C语言保持兼容
• 什么时候用结构体而不用类
▫ 定义主要用来保存数据、而没有什么操作的类型
▫ 人们习惯将结构体的数据成员设为公有,因此这时
用结构体更方便 
结构体的定义和初始化
• 结构体定义
struct 结构体名称 {
公有成员
protected:
保护型成员
private:
私有成员
};
• 一些结构体变量的初始化可以用以下形式
类型名 变量名 = { 成员数据1初值, 成员数据2初值, …… };
 
 
联合体
• 声明形式
union 联合体名称 {
  公有成员
protected:
  保护型成员
private:
  私有成员
};
• 特点:
▫ 成员共用相同的内存单元
▫ 任何两个成员不会同时有效
联合体的内存分配
union Mark { //表示成绩的联合体
char grade; //等级制的成绩
bool pass;
//只记是否通过课程的成绩
int percent;//百分制的成绩
};
 
无名联合
例:
union {
int i;
float f;
}
在程序中可以这样使用:
i = 10;
f = 2.2;
 
钟表类的代码
#include<iostream>
using namespace std;
class Clock{
    public:
        Clock(int newH= 0, int newM= 0, int newS= 0); //构造函数 
        Clock(const Clock &C); 
        void setTime(int newH= 0, int newM= 0, int newS= 0);
        void showTime(); 
    private:
        int hour,minute,second;
}; 
void Clock::setTime(int newH ,int newM,int newS) {
    hour = newH, minute = newM, second = newS; 
}
Clock::Clock(int newH , int newM, int newS) {
    hour = newH, minute = newM, second = newS; 
}
Clock::Clock(const Clock &C){
    hour = C.hour, minute = C.minute, second = C.second;
} 
void Clock::showTime(){
    cout<<hour<<":"<<minute<<":"<<second<<endl;
}
int main() {
    Clock myClock;
    myClock.setTime(8, 30, 30);
    Clock NewClock = myClock;
    NewClock.showTime(); 
    myClock.showTime();
    return 0; 
}

点类型定义

#include<iostream>
using namespace std;
class Point { //Point 类的定义
    public:
        Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数,内联
        Point(const Point& p); //复制构造函数
        void setX(int xx) {x=xx;}
        void setY(int yy) {y=yy;}
        int getX() const { return x; } //常函数(第5章)
        int getY() const { return y; } //常函数(第5章)
    private:
        int x, y; //私有数据
};
//成员函数的实现
Point::Point (const Point& p) {
    x = p.x;
    y = p.y;
    cout << "Calling the copy constructor " << endl;
} 
//形参为Point类对象的函数
void fun1(Point p) {
    cout << p.getX() << endl;
}
//返回值为Point类对象的函数
Point fun2() {
    Point a(1, 2);
    return a;
}
//主程序
int main() {
    Point a(4, 5); //第一个对象A
    cout<<"OOOOO"<<endl;
    Point b = a; //情况一,用A初始化B。第一次调用复制构造函数
    cout<<"11111"<<endl;
    cout  << b.getX() << endl;
    fun1(b); //情况二,对象b作为fun1的实参。第二次调用复制构造函数
    cout<<"22222"<<endl;
    b = fun2(); //情况三,函数的返回值是类对象,函数返回时调用复制构造函数//编译器优化过了,这里不会调用 
    cout<<"33333"<<endl;
    cout  << b.getX() << endl;
    return 0;
}

 

   线类:
//4_4.cpp
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point类定义
    public:
    Point(int xx = 0, int yy = 0) {
        x = xx;
        y = yy;
    }
    Point(Point &p);
    int getX() { return x; }
    int getY() { return y; }
    private:
    int x, y;
};
Point::Point(Point &p) { //复制构造函数的实现
    x = p.x;
    y = p.y;
    cout << "Calling the copy constructor of Point" << endl;
}

//类的组合
class Line { //Line类的定义
    public: //外部接口
        Line(Point xp1, Point xp2);
        Line(Line &l);
        double getLen() { return len; }
    private: //私有数据成员
        Point p1, p2; //Point类的对象p1,p2
        double len;
};
//组合类的构造函数
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
    cout << "Calling constructor of Line" << endl;
    double x = static_cast<double>(p1.getX() - p2.getX());
    double y = static_cast<double>(p1.getY() - p2.getY());
    len = sqrt(x * x + y * y);
}
Line::Line (Line &l): p1(l.p1), p2(l.p2) {//组合类的复制构造函数
    cout << "Calling the copy constructor of Line" << endl;
    len = l.len;
} 
//主函数
int main() {
    Point myp1(1, 1), myp2(4, 5); //建立Point类的对象
    Line line(myp1, myp2); //建立Line类的对象
    Line line2(line); //利用复制构造函数建立一个新对象
    cout << "The length of the line is: ";
    cout << line.getLen() << endl;
    cout << "The length of the line2 is: ";
    cout << line2.getLen() << endl;
    return 0;
}

 

将变量和函数限制在编译单元内
• 使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。
namespace {
//匿名的命名空间
int n;
void f() {
n++;
}
}
• 这里被“namespace { …… }”括起的区域都属于匿名的命名空间。
 
标准C++库
• 标准C++类库是一个极为灵活并可扩展的可重用软件模块的集合。标准C++类与组件在逻辑上分为6种类型:
▫ 输入/输出类
▫ 容器类与ADT(抽象数据类型)
▫ 存储管理类
▫ 算法
▫ 错误处理
▫ 运行环境支持
 
编译预处理
• #include 包含指令
▫ 将一个源文件嵌入到当前源文件中该点处。
▫ #include<文件名>
 按标准方式搜索,文件位于C++系统目录的include子目录下
▫ #include"文件名"
 首先在当前目录中搜索,若没有,再按标准方式搜索。
• #define 宏定义指令
▫ 定义符号常量,很多情况下已被const定义语句取代。
▫ 定义带参数宏,已被内联函数取代。
• #undef
▫ 删除由#define定义的宏,使之不再起作用。
 
#if 常量表达式1
程序正文1    //当“ 常量表达式1”非零时编译
#elif 常量表达式2
程序正文2    //当“ 常量表达式2”非零时编译
#else
程序正文3    //其他情况下编译
#endif
 
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1,否则编译程序段2。
 
条件编译指令(续)
#ifndef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。
 
 
 
 
posted @ 2020-03-23 14:47  SunCY  阅读(263)  评论(0编辑  收藏  举报