【C++】第四章 类

本文详细介绍了C++中的类,包括类的设计、特殊成员函数(构造函数、析构函数、复制构造函数、赋值运算符)、类的常量、运算符重载、友元、继承(单继承、多继承、多重继承)以及嵌套类。重点讨论了构造函数的定义方式、默认构造函数、析构函数的作用、拷贝构造函数和赋值运算符的重载、const成员函数的使用、静态成员函数以及友元的三种形式。此外,还涵盖了继承的访问级别、多态的概念、虚函数和纯虚函数的用法,以及如何处理继承中的二义性问题。文章最后提到了嵌套类和局部类的定义与特点。
摘要由CSDN通过智能技术生成

该文章内容整理自《C++ Primer Plus(第6版)》、《Effective C++(第三版)》、以及网上各大博客

类的设计

C++对结构进行了扩展,结构和类唯一的区别是,结构默认访问类型是public,而类为private

成员函数定义时,有两种定义方式,一种是直接在类声明内部定义函数,此时函数都将会自动成为内联函数;第二种是在类声明外定义,此时需要在函数名前加上所属类以及作用域运算符,如void Stock::update(double price) {}。要想在类声明外定义的函数为内联函数只需要在定义前加上inline限定符就可以了(另外,由于内联函数的特殊规则要求每个使用他们的文件中都对其进行定义,因而要确保内敛定义对多个文件都可用,最简单的方法是将内敛定义放到定义类的头文件中)

每个类对象都有自己的存储空间用于存储其内部变量和类成员,但对于用一个类的所有对象共享同一组类方法

特殊成员函数

C++会自动提供一些特殊的成员函数:

  • 默认构造函数,如果没有定义构造函数
  • 默认析构函数,如果没有定义
  • 复制构造函数,如果没有定义
  • 赋值运算符,如果没有定义
  • 地址运算符,如果没有定义
  • 移动构造函数,C++11提供
  • 移动赋值运算符,C++11提供

在C++11中提供了default关键字来控制默认函数的使用。假如要使用某个默认函数,但是这个函数由于某种原因不会自动创建,如编写了移动构造函数,因而编译器不会自动创建默认构造函数、复制构造函数、和复制赋值构造函数,此时可使用default显式声明这些方法的默认版本,而编译器将为这些函数自动生成函数体。这种特性只适用于类的特殊成员函数。如

class Test {
public:
	Test(Test &&);
	Test() = default;
	Test(const Test &) = default;
	Test & operator=(const Test &) = default;
}

另一方面,C++11还提供了delete来禁止编译器使用特定方法。如要禁止复制对象,可禁用复制构造函数和复制赋值构造函数。注意default仅适用于特殊成员函数,而delete则可用于任何成员函数。如

class Test {
public:
	Test() = default;
	Test(const Test &) = delete;
	Test & operator=(const Test &) = delete;
}

要禁止复制,还可以将复制构造函数和赋值运算符放在private部分,但是使用delete则会更容易理解
delete的另外一个用法是禁止特定的转换。如下面程序,若仅有参数类型为double的函数,func(5)则会将5转换为5.0再调用。但是此时检测到func(int)被禁用后则会将func(5)当做编译错误

class Test {
public:
	void func(double);
	void func(int) = delete;
}

构造函数

构造函数专门用于构造新对象,将值赋给它们的数据成员。结构函数的名称与类名相同,没有返回值以及声明类型,位于类声明的公有部分

调用构造函数

//显示调用
Stock food = Stock("World Cabbage", 250, 1.25);
//隐式调用
Stock garment("Furry Mason", 50, 2.5);
//new创建
Stock *pstock = new Stock("Electroshock Games", 18, 19);

//也可以用列表初始化
Stock a1 = {"A1", 1, 1.1};
Stock a2 {"A1", 1, 1.1};
Stock a3 {};

//另外,当构造函数只有一个形参时允许直接使用等号进行赋值
Stock tmp = 1;

C++中的explicit关键字只能用于修饰只有一个参数或只有第一个参数没有默认参数的类构造函数,它的作用是表明该构造函数是显式的不能进行隐式类型转换,跟它相对应的另一个关键字是implicit。类构造函数默认情况下即声明为implicit。如上面的Stock tmp = 1;就有从int到Stock的隐式转换,在构造函数名前加上explicit关键字后则禁止了这种转换,此时要只能进行显式转换Stock tmp = (Stock)1;

默认构造函数是指未提供显式初始值时用来创建对象的构造函数。默认构造函数定义方式有两种,一种是不接受任何参数,另一种是所有参数都提供默认值。当没有提供任何构造函数时C++会自动提供默认构造函数,它是默认构造函数的隐式版本。但当提供有非默认构造函数时则不会自动提供默认构造函数,此时直接定义对象而不赋予初始值则会发生错误

Stock first("Concrete Conglomerate");//调用非默认构造函数
Stock second();//声明无参数且返回值为Stock类型的函数
Stock third;//调用默认构造函数

注意构造函数也能被声明为protected或private,此时外部就不能创建这个类实例或者从这个类派生(当声明为protected时则能够派生)。因为只能从内部访问到类的构造函数,因而此时只能通过static成员函数或者友元来创建类对象,同时,还能在创建时进行某些限定,如限定程序中最多只能创建特定数量的类对象

在C++11中,为了简化构造函数的编写,提供了委托构造函数和继承构造函数。委托构造函数是指在一个构造函数的定义中使用另一个构造函数,因为构造函数暂时将创建对象的工作委托给另一个构造函数,如

class Notes {
public:
	Notes();
	Notes(int);
	Notes(int, double);
	Notes(int, double, string);
private:
	int k;
	double x;
	string st;
}
Notes::Notes(int _k, double _x, string _st) : k(_k), x(_x), st(_st) {};
Notes::Notes() : Notes(0, 0.01, "A") {};
Notes::Notes(int _k) : Notes(_k, 0.01, "A") {};
Notes::Notes(int _k, double _x) : Notes(_k, _x, "A") {};

而继承构造函数则能进一步简化编写工作。首先在C++98中允许这么编写成员函数。此时,using表明使用了A命名空间中的func函数以及其所有重载版本,此时在B中有三个func函数,但是double类型的版本是使用B中的版本。

class A {
    int func(int);
    double func(double);
    void func(const char* s);
}
class B : class A {
    using A::func;
    double func(double);
}

而在C++11中将这种方法用于构造函数,让派生类继承基类的所有构造函数(默认构造函数、复制构造函数和移动构造函数除外),但不会使用与派生类构造函数的特征标匹配的构造函数。如下面程序,此时B tmp(1, 1.1);会调用基类A中对应的构造函数,但是此时只会给基类数据成员初始化

class A {
public:
    A();
    A(int);
    A(double);
    A(int, double);
}
class B : class A {
public:
    using A::A;
    B();
    B(int);
    B(double);
}

析构函数

析构函数一般用于delete在构造函数中使用new分配的内存空间。若构造函数中没有使用new则只需让编译器自动生成一个什么都不做的隐式析构函数即可。析构函数名称和构造函数一样,只需要再在函数名前加~

因为析构函数只有一个而构造函数可以有多个,所以当构造函

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值