数据表示和操纵数据的方法组合成一个简洁的包。类规范有两个部分组成:
- 类声明:以数据成员的方式描述数据部分,以成员函数的方式描述公有接口。
- 类方法定义:描述如何实现类成员函数。
- 访问控制:private和public。实用类对象的程序都可以直接访问共有部分,但只能通过共有成员函数访问对象的私有成员。
- 控制对成员的访问:数据项通常放在私有部分,组成类接口的成员函数放在共有部分。
类和结构体区别:结构默认的访问级别是public,类是private。
实现类成员- 定义成员函数时,使用 :: 作用域解析运算符标识所属类。
- 类方法可以访问类的private组件。
所创建的每一个对象都有自己的存储空间,用于存储其内部变量和类成员;但同一个类的所有对象共享同一组类方法,每种方法只有一个副本。
使用类 可以声明类变量,也可以使用new为类对象分配存储空间。
类声明包括数据成员和函数成员,典型的类声明格式如下:
class className
{
private:
data member declarations
public:
member function prototypes
};
类的构造函数和析构函数
声明和定义构造函数
专门用于构造新对象,将值赋给它们的数据成员。构造函数被用来创造对象,而不能用来通过对象调用。隐式的调用默认构造函数时,不要使用圆括号。
析构函数 对象过期时,程序自动调用。析构函数没有参数。
C++可以使用列表初始化类
Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
Stock jock {"Sport Age Storage; Inc"};
Stock temp {};
Stock::Stock(const std::string & co, long n = 0, double pr = 0.0);
const成员函数 类方法不修改调用对象
void Stock::show() constthis指针
this指针指向用来调用成员函数的对象。每个成员函数都有一个this指针。
对象数组 声明对象数组的方法与声明标准类型数组类似;可以使用构造函数来初始化元素数组。
Stock mystuff[4];
Stock stocks[2] = {
Stock("NanoSmart", 12.5, 20),
Stock("Boffo Objects", 200, 2.0)
};
类作用域
关于符号常量,在创建对象之前没有用于存储值的空间。两种方式
- 在类中声明枚举,枚举不会创建类数据成员,所有对象都不包含枚举。遇见时,编译器会将其替换为相应的值。
- static,静态变量不会存储在对象中。
class Bakery
{
private:
enum {Mouths = 12};
...
};
class Bakery
{
private:
static const int Mouths = 12;
...
};
总结
一般来说,私有数据成员存储信息,公有函数提供访问数据的路径,类将数据和方法组成一个单元,其私有性实现数据隐藏。
使用类 运算符重载 用户可以定义多个名称相同但特征标不同的函数。不要返回指向局部变量或临时对象的引用。
一个time类的加法重载运算符:
Time Time::operator+(const Time& t) const {
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes / 60;
sun.minutes %= 60;
return sum;
}
// 调用方式
total = object1.operator+(object2);
total = object1 + object2;
重载限制
- 重载后的运算符必须至少有一个操作数是用户定义的类型。
- 使用运算符时不能违反运算符原来的语法规则。
- 不能创建新的运算符。
- 不能重载的运算符:sizeof . .* :: ?: typeid const_cast dynamic_cast reinterpret_cast static_cast
- 只能通过成员函数重载的运算符。
一种访问权限的形式:友元函数,友元类,友元成员函数。
友元函数的声明:
friend Time operator*(double m, const Time & t);
定义:
Time operator*(double m, const Time & t) {
Time result;
long totalminutes = t.hous * mult * 60 + t.minutes * mult;
result.hours = totalminutes / 60;
result.minutes = titalminutes % 60;
return result;
}
常用友元
重载<<运算符:ostream应该作为第一个操作数,但是重载有限制,需要用友元。
void operator<<(ostream & os, const Time & t) {
os << t.hours << " hours, " << t.minutes << " minutes";
}
但是无法执行cout << “Trip” << trip << " Tuesdayn";
ostream & operator<<(ostream & os, const Time & t) {
os << t.hours << " hours, " << t.minutes << " minutes";
}
这样就可以了
定义运算符时,必须选择一种格式,而不能同时选择两种格式,不然会产生二义性的错误。
类的自动转换和强制类型转换只有接受一个参数的构造函数才能作为转换函数,explicit 关闭class的隐式转换
转换函数构造函数只用于从某种类型到类类型的转换,若想要进行相反的转换,必须使用特殊的C++运算符——转换函数。
转换函数是用户定义的强制类型转换
- 转换函数必须是类方法
- 不能指定返回类型
- 转换函数不能有参数
operator typeName();总结
访问私有类成员的唯一方法是使用类方法。c++使用友元函数来避免这种限制。在声明时加上friend关键字。
类和动态内存分配Note:使用一个对象初始化另一个对象时,编译器将自动生成上述构造函数(复制构造函数)
特殊的成员函数c++自动提供以下成员函数:
- 默认构造函数,如果没有定义构造函数
- 默认析构函数,如果未定义
- 复制构造函数,如果未定义
- 赋值运算符,如果未定义
- 地址运算符,如果未定义
Class_name(const Class_name &)静态成员函数
不能通过对象调用静态函数,实际上静态函数甚至不能使用this指针。静态函数不与特定的对象相关联,因此只能使用静态数据成员
在构造函数中使用new应注意- 在构造中使用new初始化指针成员,析构函数中使用delete。
- new和delete必须互相兼容。new对应于delete,new[]对应于delete[]。
- 如果有多个构造函数,则必须以相同的方式使用new,因为只有一个析构函数。
- 应该定义一个复制构造函数。
- 应当定义一个赋值运算符。
class_name是类,value是Type_name,下面的语句:
Class_name * pclass = new Class_name(value);
将调用构造函数:
Class_name(Type_name);
也许包含琐碎的转换:
Ex:Class_name(const Type_name &);
最后调用默认构造函数:
Class_name * ptr = new Class_name;
Note:
-
使用常规表示法来声明指向对象的指针:
String * glamour;
-
可以将指针初始化为已有的对象:
String * first = &sayings[0];
-
可以使用new来初始化指针
String * favorite = new String(sayings[choice]);
-
对类使用new将调用相应的类构造函数来创建对象
String * gleep = new String;
-
可以使用->运算符通过指针访问类方法
if (sayings[i].length() < shortest->length())
-
解引用获得对象
*object
派生类具有的特征:
- 派生类对象存储了基类的数据成员
- 派生类对象存储可以使用基类的方法
派生类需要添加的特性:
- 派生类需要自己的构造函数
- 需要添加额外的数据成员和成员函数
创建派生类对象时,程序首先创建基类对象
多态共有继承实现多态共有继承:
- 在派生类中重新定义基类的方法
- 使用虚方法
使用virtual,如果方法是通过引用或指针而不是对象调用。如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法,如果使用了virtual。程序将根据引用或指针指向的对象的类型来选择方法。
Note:如果要在派生类中重新定义基类方法,通常应将基类方法声明为虚的。程序将根据对象类型而不是引用或指针的类型类选择方法版本。非构造函数不能使用成员初始化列表语法。
为何需要虚析构函数?
如果析构函数不是虚的,则将只有调用对应于指针类型的析构函数。如果是虚的,将调用相应对象类型的析构函数。使用虚析构函数可以确保正确的析构函数序列被调用。
静态联编和动态联编 指针和引用类型的兼容性Employee veep; //基类 Singer trala; //派生类 Employee * pe = &trala; //允许向上隐式类型转换 Singer * ps = (Singer *) &veep; //必须向下显示类型转换为什么有两种类型的联编以及为什么默认为静态联编
效率。为了使程序能够在运行阶段进行决策,必须采取一些方法来跟踪基类指针或引用指向的对象类型。C++的指导性原则之一,不要为不使用的特性付出代价。
虚函数的工作原理编译器处理虚函数的方法,给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。数组被称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。
使用虚函数时:
- 每个对象都将增大,增大量为存储地址的空间
- 对于每个类,编译器都将创建一个虚函数地址表
- 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。
Note:
- 在基类的方法中声明使用关键字virtual可使该方法在基类以及所有派生类中时虚的
- 如果使用只想对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法。
- 如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。
对于虚方法,构造函数不能是虚函数;析构函数应当是虚函数,除非类不用做基类;友元不能作为虚函数,友元不是类成员,而只有成员才能使虚函数;如果派生没有重新定义函数,将使用该函数的基类版本。
两条经验规则:
- 如果重新定义的继承方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。
- 如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。
protected和private类似,在类外只能用公有类成员来访问protected部分中的类成员。区别仅在基类派生的类中才会表现出来。派生类成员可以直接访问基类的保护成员,但不能访问基类的私有成员。
抽象基类当类声明中包含纯虚函数时,不能创建该类的对象
继承和动态内存分配 派生类不使用new不用显示的定义析构函数,复制构造函数和赋值运算符。
派生类使用new必须定义显示析构函数,复制构造函数和赋值运算符。
回顾 编译器生成的成员函数 默认构造函数要么没有参数,要么所有参数都有默认值。没有,编译器将会生成默认的构造函数。如果派生类构造函数的成员初始化列表没有显示的调用基类的构造函数,则编译器将使用基类的默认构造函数来构造派生类对象的基类部分。
复制构造函数复制构造函数接受其所属类的对象作为参数。
Star(const Star &);
下属情况使用复制构造函数:
- 将新对象初始化为一个同类对象
- 按值将对象传递给函数
- 函数按值返回对象
- 编译器生成临时对象
默认的赋值构造函数用于处理同类对象之间的赋值。
类函数小结
| 函数 | 能否继承 | 成员还是友元 | 默认能否生成 | 能否为虚函数 | 是否可以有返回类型 |
|---|---|---|---|---|---|
| 构造函数 | 否 | 成员 | 能 | 否 | 否 |
| 析构函数 | 否 | 成员 | 能 | 能 | 否 |
| = | 否 | 成员 | 能 | 能 | 能 |
| & | 能 | 任意 | 能 | 能 | 能 |
| 转换函数 | 能 | 成员 | 否 | 能 | 否 |
| () | 能 | 成员 | 否 | 能 | 能 |
| [] | 能 | 成员 | 否 | 能 | 能 |
| -> | 能 | 成员 | 否 | 能 | 能 |
| op= | 能 | 任意 | 否 | 能 | 能 |
| new | 能 | 静态成员 | 否 | 否 | void* |
| delete | 能 | 静态成员 | 否 | 否 | void |
| 其他运算符 | 能 | 任意 | 否 | 能 | 能 |
| 其他成员 | 能 | 成员 | 否 | 能 | 能 |
| 友元 | 否 | 友元 | 否 | 否 | 能 |



