第10~13章:类基础

  • 将类定义在头文件之中,而将类方法的具体实现放在源文件中。
  • 在函数实现中通过作用域运算符::表明所属类。
class Stock
{
private:
    int n;
    void show(int s);
    ...
}
void Stock::show(int s)
{
    ...
}
  • 定义位于类声明中的函数自动成为内联函数,在类外面的函数也可以使永inline成为内联函数。
构造函数与析构函数
  • 构造函数无返回值,函数名必须和类名保持一致。
  • 为构造函数提供重载版本,或者提供默认值,使得支持多样化赋值。
  • 构造函数还可以用于创建临时对象。
  • 析构函数只能有一个,并且如果要使用new,必须提供析构函数。
class Stock
{
    ...
	Stock() = default;
	Stock(const std::string& os, int n, double p);
	~Stock()
	{
	    cout << "bye";
	}
}
Stock::Stock(const std::string& os, int n, double pri)
{
	company = os;
	shares = n;
	share_val = pri;
}

在函数命名后面添加const,使得函数不对隐式对象进行任何修改。

void show(int n) const
{
    cout <<company <<" ..."<<n<<endl;
}
//可以对n进行修改,但不会对调用该函数的对
//象进行任何修改
认识this指针
const Stock& Stock::Stock_cmp(const Stock& a)const
{
	if (a.shares > shares)return a;
	else return *this;
}
  • this指针是指向调用当前函数的对象的地址。
在类内定义常量,使所有对象共用。用来创建数组
  1. 使用enum枚举
class Test
{
    enum{time = 60}
}
  1. 使用static关键字
static const int time =60;

下面不支持的

const int time =12;
//未创建对象前,未分配内存。

运算符重载

operation 运算符(参数表)

class Node
{
    int x,y;
public:
    Node operator+(const Node &a)const
}
Node Node::operator+(const Node &a)const
{
    Node c;
    c.x = x  + a.x;
    c.y = y + a.y;
    return c;
}
操作者的左侧是函数的调用者。
重载运算符的限制:
  • 重载的运算符必须要有一个操作数是用户自己定义的类型.
  • 使用运算符不能违背句法规则。即一元运算符不能变成二元运算符,反之亦是。
  • 不能修改运算符优先级。
  • 不能创造运算符。
  • 不能重载以下运算符:
  • sizeof
  • 成员运算符 .
  • ::,作用域运算符
  • ?:,条件运算符
  • typeid,RITI运算符
  • const_cast、dynamic_cast、reinterpret_cast、static_cast四个强制类型转换符号

下面的运算符必须作为成员函数重载

= 赋值, []下标,()函数调用,->指针成员


友元

赋予函数以类成员函数的权限

不能通过成员运算符 . 来访问

必须在类中声明

友元函数不是成员函数,没有this指针。

class Name
{
    ...
    friend Name MyFunction(...)
}
Name MyFunction(...)
{
    ...
}

重载 <<
1.(不推荐)

class Test
{
    ...
    friend void operator<<(ostream &os,const Test &a)
    {
        os << "asdfad"<< a.... 
    }
}

但是我们可以发现这不能用于以下表达
cout << “asdf” << a <<endl;
所以我们可以把返回值设为 ostream &;

class Test
{
    ...
    friend ostream & operator<<(ostream &os,const Test &a)
    {
        os << a...;
    }
}

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

类使用构造函数来实现隐式的类型转换。

class Test
{
    ...
public:
    Test(int n)
    {
        ...
    }
}
    Test n;
    n = 10;
//因为Test提供了关于int的构造函数,
//那么int就可以转换为Test类型。

//有时候我们不想要隐式的类型转换,可以使用
//explicit来修饰构造函数。
explicit Test::Test(int n)
{
    ...
}

使用explicit关键词限制隐式类型转化。

  • 构造函数只能实现到类类型的转化,类转化为其他类型数据要用转换函数
  • 格式: operator typename ();

注意:

  • 转换函数必须是类方法
  • 不能指定返回类型
  • 不能有参数

转换为double:

operator double()
{
   ... 
}
  • 转换函数也可以使用explicit来限制必须显示转换,甚至建议这么做,避免不经意的错误。

类的动态内存分配:

将类成员设为指针,在构造函数是用new分配内存,析构函数释放内存。

当使用对象来初始化另一个对象的时候,编译器执行默认复制构造函数。默认构造函数逐个复制非静态元素。如果成员函数是类,优先调用复制构造函数,如果没有那么执行默认构造函数。
 People Jane(Jack);
 People Jane = Jack;
 People Jane = People(Jack);
 People *Jane = new People(Jack)

以上都会调用复制构造函数

显式定义复制构造函数

People::People(const People &person)
{
    age = person.age;
    ....
}

如果对象是指针的话,默认构造函数很可能会释放两次内存,导致程序崩溃。

class People
{
    char * name
    ...
pulic:
    People(const char * s);
    ~People();
}
People::People(const char *s)
{
    int len = strlen(s);
    name = new char[len+1];
    name = s;
}
People::~ People()
{
    delete [] name;
}

使用默认构造函数,会将name的地址传递,那么调用析构函数时候就会将该处地址释放两次,引起错误。

默认的赋值运算符也是逐个复制非静态成员,显然也可以出现这样的错误。因此我们也要重载赋值运算符 =
如果不愿意写复制构造函数,又不愿意使用默认复制或者赋值方法,可以显式的将他们定义为private私有的,这样就禁止使用。
People & People::operator =(const People &s)
{
    int len = strlen(s.name);
    delete [] name;
    name = new char [ len + 1];
    name = strcpy(name,s.name);
    ...
    
    return *this ;
}

在构造函数中使用new,所有构造函数的new必须一致,因为析构函数只有一个,所有要么使用delete [] ,要么是delete,而构造函数必须和它匹配。

  • 类构造函数初始化列表;
class People
{
    static int Size ;
    People(int siz,...)
}
People::People(int siz):Size(siz)
{
    ...
}
//因为static在所有对象中只初始化一次,所以必须使用这样的初始化。
//或者显式

类继承

公有继承
class Chinese:public People
{
    
}
关系
  • 派生类可以使用基类的方法。派生类不能直接使用基类的私有成员,而通过基类的公有类方法访问基类的私有成员。
  • 基类指针可以不进行显式转化下指向派生对象;
  • 基类引用可以不进行显式转化下引用派生类对象。

注意:

  • 派生类在使用构造函数初始化时候,必须使用基类的构造函数初始化基类成员,而不能直接为基类成员赋值。
  • 公有继承用于实现一种 is-a-kind-of,简称is-a的关系。
多态公有继承

如果希望一个方法在基类和派生类的行为不一致,即方法行为取决于方法对象。

  1. 通过在派生类中重新定义基类的方法实现。
  2. 使用虚函数。
  3. 在类中声明函数时,在函数返回值之前添加virtual,使函数成为虚函数。

不使用虚函数,将会通过判断指针和引用的类型来调用相应的类方法,而使用虚函数之后,将通过判断指针和引用指向的对象类型调用相应类方法。

//Chinese为People派生类。
People Jane(...);
Chinese CF;
People &P1 = Jane;
People &p2 = CF;
P1.age();
P2.age();
//如果不是虚方法,那么将调用People的age方法,
//如果定义为虚方法,那么P1将调用People的age,P2调用Chinese的age。
动态联编与静态联编

将代码中的函数调用解释为执行特定的函数代码块称为函数名联编(blinding)。在编译时候进行联编的称为静态联编,然而虚函数的存在使得静态联编变得困难,因而引入了与之对应在程序执行是进行联编的动态联编。

  • 在基类声明中使用virtual,可以使得该方法在所有派生类和基类都是虚的。
  • 构造函数不能是虚函数,但析构函数应当是虚函数。
  • 在派生类中重新定义类方法将覆盖基类的方法,所有会有以下经验规则:
  1. 重新定义方法应当确定与原来的原型相同,但如果返回值是基类的引用或者指针,可以修改为派生类的引用与指针。
  2. 如果基类的方法是重载了,那么应该重新定义所有重载版本。避免其余版本被隐藏。
访问控制protected

protected成员与私有成员private类似,只有当涉及到派生类时才会有所不同。派生类的成员可以直接访问基类的保护成员protected,但不能访问基类的私有成员。

继承与动态内存分配(小技巧)
  • 当派生类不使用new的时候,不需要显式的为派生类定义析构函数、复制构造函数和赋值运算符。
  • 如果派生类使用了new,那么就必须显式定义。上述函数。
  • 在继承的时候,构造函数和析构函数,以及赋值运算符不会被继承。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东风中的蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值