类和对象
类和对象
• 类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
• 利用类可以实现数据的封装、隐藏、继承与派生。
• 利用类易于编写大型复杂程序,其模块化程度比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。