C++primerplus知识总结二 (类知识点)

类和对象

类实现了抽象、隐藏、多态、继承

一般把类的定义放在头文件中,类中具体函数的实现放在源代码文件中,类定义只是定义了构造类的方法,没有为类分配内存,所以类中的变量不能进行初始化和赋值。

定义位于类声明中的函数都将自动成为内联函数,也可以在类定义后声明,单要加关键字inline内联函数是在调用函数的时候不会跳转到函数的定义处,而是直接在代码块中嵌入函数。

const成员函数
void show() const;//类的show()成员函数
使用对象调用show()成员函数将保证对象不会被修改

作用域为类的常量

class Bakery
{
private:
const int Months =12;
double costs[Months]
}

这样是不行的,因为没有分配存储空间
可以在类中声明一个枚举

class Bakery
{
private:
enum{Months=12};
double costs[Months];
}

用这种方法声明枚举并不会创建类数据成员,所有对象都不包含枚举。

还有另一种定义常量的方法,使用关键字static

class Bakery
{
private:
static const int Months=12;
double costs[Months];
}

这个常量与其他静态常量存储在一起,而不是存储在对象中。

不要使用返回指向局部变量或临时变量的引用。函数执行完毕后,局部变量和临时变量对象将消失,引用将指向不存在的数据。

友元

友元有三种
1 友元函数
2 友元类
3 友元成员函数

友元函数
为类重载二元运算符时常常需要友元
由于非成员函数不能直接访问类的私有数据成员,但是可以将非成员函数声明为类的友元这样就可以使用非成员函数访问类的私有数据成员。
创建友元的方法是将原型放在类声明中,并在原型前加关键字friend

friend Time operator* (double m,const TIme & t);

然后编写函数定义,因为它不是成员函数,所以不需要使用类限定符,另外不要在定义中使用friend关键字。

友元类
假设有两个类Tv类和Remote类
Remote类能够控制Tv类,因此可以将Remote类作为Tv类的友元类,那么Remote类将可以直接访问Tv类的私有成员,而不需要通过Tv类的成员函数。

class Tv
{
public:
friend class Remote;
enum{off,on};
enum{MinVal,MaxVal=20};
enum{Antenna,Cable};
enum{Tv,DVD};
.......
}

class Remote
{
public:
....
}

友元声明可以位于私有,公有和保护部分,其所在位置无关紧要。

友元成员函数
可以将特定类的成员函数作为另一个类的友元,而不必将整个类作为友元。
加入Remote类只有一个成员函数set_chan()需要使用Tv类的私有成员,则可以将set_chan()作为友元成员函数。

class Tv
{
friend void Remote::set_chan(Tv& t,int c);
}

但是要让编译器能够处理这段数据,需要知道Remote类的定义,否则编译器不知道Remote是一个类,而且set_chan是一个类方法。所以应该让Remote类的定义放在Tv类的前边。但是在Remote方法里又提到了Tv类的对象,所以需要使用前向声明

class Tv;
class Remote
{.....}
class Tv
{.....}

这样能不能行呢?

class Remote;
class Tv{....};
class Remote{....};

这样不行,因为编译器看到Remote类的方法时,需要知道remote类和方法的声明。
但是假如在类中我们使用了内联函数,例如

void onoff(Tv& t){t.onoff()} 

但是我们的Tv类的定义是在Remote后,这也就是说编译器看到onoff后不知道这是什么。这怎么办呢,这样的话就要将内联函数定义在Tv类的后边,并加上inline关键字,这样就行了。也就是说定义在类后边的类,你可以加上前向声明,让我知道在这个类中这是个类,但是绝对不能包括成员函数。

彼此的友元
Tv类作为Remote类的友元,Remote类也作为Tv类的友元,这样定义的内联函数都要到类后边定义

类的自动转换和强制类型转换

如果构造函数只接受一个参数,或者其他的参数都提供默认值,则可以将于构造函数参数相同的类型赋给这个构造函数

Stone(double lbs);
Stone maycha=16.8;//这样是可行的

但是这样会带来许多问题,所以我们一般要关闭这种特性,就要使用explicit关键字

复制构造函数和赋值运算符

复制构造函数用于初始化过程中,把一个对象复制到新创建的对象中,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。按值传递对象意味着创建原始变量的一个副本。编译器生成临时对象,也将掉用复制构造函数。
默认的复制构造函数使用的是浅复制,复制的是成员的值,当类中有字符串指针时,将复制指针指向的地址,如果使用new运算符为字符串指针分配内存,则当使用析构函数中含有delete时,则会将这段内存释放掉。为了避免这种情况,应该使用深复制,重新定义复制构造函数,为复制的字符串指针重新开辟内存空间,使用strcpy()函数复制字符串。
如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数组而不是指针,这被称为深复制
同样对于赋值运算符也是使用的浅复制,如果要将一个对象赋给另一个对象,则应该重新定义赋值运算符进行深复制。另外需要注意的一点是,对于赋值运算符在进行深复制时,需要将原来对象字符串指针指向的内存释放,重新分配内存进行深复制。如果不释放,则之前的字符串将继续留在内存中。

静态成员函数
可以将成员函数声明为静态的,在成员函数之前加static,不能通过对象调用静态成员函数,静态成员函数不能使用this指针。如果静态成员函数是在公有部分声明的,则可以使用类名或作用域解析符来调用。

返回对象

返回对象时,有几种返回对象的方法
1 返回对象的引用
2 指向对象的const引用
3 返回const对象

如果函数返回传递给它的对象,可以通过返回引用来提高效率,返回对象将调用复制构造函数,而返回引用不会

如果被返回的对象是被调用函数中的局部变量,则不应按引用返回,因为被调用函数执行完毕时,局部对象将调用其析构函数。

类继承

派生类构造函数应该注意的地方:
1 创建派生类对象时,程序首先创建基类对象。
2 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数,非构造函数不能使用成员初始化列表。
3 派生类构造函数应初始化派生类新增的数据成员。
4 释放对象的顺序与创建对象的顺序相反,应该先调用派生类对象的析构函数

基类指针在不进行显示类型转换的情况下指向派生类对象,基类引用也可以引用派生类对象,基类指针或引用只能调用基类方法,不能调用派生类的方法。

不可以将基类对象和地址赋给派生类引用和指针。

继承关系
公有继承 public
保护继承 protected
私有继承 private

protected
protected和private的区别只有在基类派生的类中才会表现出来,在类外都是不能直接访问必须通过接口访问。但是派生类的成员可以直接访问protected的成员。
private
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。

多态
如果希望同一个方法,基类和派生类调用会产生不同的效果,就要使用多态。
有两种方法可以实现多态
1 在派生类中重新定义基类的方法
2 使用虚方法。

如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。这样程序将根据对象类型而不是引用或指针的类型来选择方法版本。
通常要为基类声明一个虚析构函数,这样可以保证释放派生对象时,按正确的顺序调用析构函数,如果析构函数不是虚的,当基类指针指向派生类时,则只会调用基类的虚构函数。如果析构函数是虚的,则先调用派生类的析构函数让后调用基类的析构函数。

虚函数的工作原理
编译器处理虚函数的方法是给每一个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组成为虚函数表,虚函数表中存储了为类对象进行声明的虚函数的地址。

虚函数应该注意的事项
1 在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类中是虚的。
2 如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编。
3 如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。
4 构造函数不能是虚函数,析构函数应当是虚函数,除非类不用做基类,通常应给基类提供一个虚析构函数,即使它并不需要析构函数。

如果重新定义继承的方法,应确保与原来的类型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变,因为允许返回类型随类类型的变化而变化。

如果基类声明被重载了,则应在派生类中重新定义所有的基类版本,如果只定义一个版本,另外两个版本将被隐藏。

抽象基类

假如有两个类,这两个类之间有相同的部分,但不能通过派生,则可以重新定义一个类,将两个类中相同的部分作为基类的数据然后这两个类作为这个类的派生类。

class BaseEllipse
{
private:
double x;
double y;
public:
BaseEllipse(double x0=0,double y0=0):x(x0),y(y0){}
virtual ~BaseEllipse(){}
void Move(int nx,int ny){x=nx;y=ny};
virtual double Area()const=0;//纯虚函数
}

BaseEllipse类作为Ellipse类和Circle的共同的基类含有纯虚函数,这样的类称为抽象基类。
当类声明中包含纯虚函数时,不能创建该类的对象。包含纯虚函数的类只用作基类。
使用BaseEllipse类就能够创建Ellipse对象和Circle对象,但不能创建BaseEllipse对象,可以使用BaseEllipse指针数组同时管理这两种对象。

基类和派生类的动态内存分配
但基类定义复制构造函数和赋值运算符时,如果派生类没有使用new则不必为派生类重新定义复制构造函数和赋值运算符。如果派生类中使用new,则要为派生类重新定义复制构造函数和赋值运算符,并使用成员初始化列表初始化基类的部分。

包含对象的成员
对于成员对象,构造函数使用成员初始化列表

student(const char* str,const double *pd,int n):name(str),scores(pd,n){}

使用成员名进行初始化。
当成员初始化列表中包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。

私有继承与包含类似,都是has-a关系,和包含不同,私有继承派生类初始化时,对于基类的初始化使用的是类名而不是成员名,私有继承使得能够使用类名和作用域解析符来调用基类的方法。如果想直接使用对象,可以通过强制类型转换。

const string& Student::Name()const
{
teturn (const sring&)*this;
}

student类继承自string类。

在私有继承中,在不进行显示类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。

使用保护成员或私有成员时,要让基类的方法在派生类外可用,方法一是定义一个使用该基类方法的派生类方法,另一种方法是使用using声明来制定派生类可以使用特定的基类成员。

class student:private std::string,private std::valarry<double>
{
public:
using std::valarry<double>::min;
using std::valarry<double>::max;
}

这样min方法和max方法在student类中就像它的公有方法一样。

多重继承

假设类b和类c的基类都是a,而d继承自类b和类c,则类d就会有两个基类对象,
为了使d只有一个基类,需要使用虚基类。

class Singer:vitual public Worker{};
class Waiter:virtual public Worker{};
class SingerWaiter:public virtual Worker{};

如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。

SingerWaiter(const Worker&wk,int p=0,int v=Singer::other):Worker(wk),waiter(wk,p),Singer(wk,v){}

对于多重继承,如果没有重新定义虚函数,则会产生二义性,因此应该重新定义,并指明是哪一个的虚函数

void Singingwaiter::show()
{
Singer::Show();
}

类模板

template <typename Type>
class Stack
{
private:
...
Type items[10];
...
public:
Stack();
bool isempty();
}

template<typemane Type>
Stack<Type>::Stack()
{
}
template<typename Type>
bool Stack<Type>::isempty()
{
}

Stack<int> ker;
Stack<string> col;

假如Type的类型为指针,应该让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。

template<class Type>
class Stack
{
private:
    enum{size=10};
    int stacksize;
    Type* items;
    int top;
public:
    explicit Stack(int ss=size);
    Stack(const Stack &&st);
    ~Stack(){delete [] items;}
    bool isempyt(){return top==0;}
    bool isfull(){return top==stacksize;}
    bool push(const Type& item);
    bool pop(Type& item);
    Stack & operator=(const Stack& st);

};
template<class Type>
Stack<Type>::Stack(int ss):stacksize(ss),top(0)
{
    items=new Type[stacksize];
}

数组模板
类里的成员有数组,可以定义类模板,其中第二个参数指定数组大小。

template<class T,int n>
class ArrayTp
{
private:
    T ar[n];
public:
    ...
};

class 指出T为类型参数,int指出n的类型为int,这种参数称为表达式参数。
表达式参数有一些限制,表达式参数可以是整数、枚举、引用或指针。
模板代码不能修改参数的值,也不能使用参数的地址。

表达式参数方法的主要缺点是,每种数组大小都将生成自己的模板

ArrayTp<double,12>egg;
ArrayTp<double,12>dou;

将生产两种类型的模板

可以将用于常规类的技术用于模板类。模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。

递归使用模板
template

template<template<typename T>class  Thing>
class Crab
{
private:
Thing<int>s1;
Thing<double>s2;
public:
.....
}

Crab包含的两个成员 Things1,Things2
其中s1,s2都是一个模板类

模板类与友元
模板类声明也可以有友元,模板的友元分为三类
1 非模板友元
2 约束模板友元,即友元的类型取决于类被实例化时的类型
3 非约束模板友元 即友元的所有具体化都是类的每一个具体化的友元

假设要为友元函数提供模板类参数,则要使用具体化

template<class T>
class HasFriend
{
friend void report(HashFriend<T>&)
}

约束模板友元函数
让友元函数本身称为模板。要使类的每一个具体化都获得与友元匹配的具体化
首先,在类定义的前面声明每个模板函数
templatevoid counts();
templatevoid report(T&);
template
class HasFriendT
{
friend void counts();
friend void report<>(HasFriendT&);

模板类的非约束模板友元函数
template
class ManyFriend
{
template

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值