Tizeng's blog Ordinary Gamer

C++基础3——类

2019-02-18
Tizeng
C++

记录一些C++中关于类的零碎知识点。

1.结构体和类

structclass本质上只有两点区别:

  • 默认继承权限不同。 class的继承如果不做指定,默认为private继承,而struct中默认为public继承。

  • 成员的默认访问权限不同。 class中的成员变量和成员函数默认权限是private,而struct中默认权限是public

2.构造函数

  • 类的构造函数是一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称必须完全相同,并且不会返回任何类型,也不会返回void
  • 构造函数可用于为某些成员变量设置初始值。
  • 只有一个参数的构造函数可以将那个参数类型的实例隐式转换为类的实例,这可以为我们在使用类的实例为参数的函数中带来方便,在构造函数的声明之前加上explicit关键字,可以禁止这种隐式转换
  • 析构函数和构造函数对应,只是在定义时在函数明前加~关键字,用于在程序结束时释放内存等。
  • 构造函数不能声明为const,实际上const对象只有在构造函数初始化完对象时才会被看做是const
  • 默认(default)构造函数只有在没有任何其他构造函数定义时被编译器自动生成

带参数的构造函数

构造函数可以在定义时根据需要加入若干参数,用于对成员变量初始化。假设有一个类A,其中有X、Y、Z三个成员变量(字段),我们可以通过以下初始化列表来简化构造函数:

A::A( double a, double b, double c): X(a), Y(b), Z(c){}

对于const和引用变量这些必须被初始化的成员来说,只能使用初始化列表来进行初始化,而不能在构造函数内初始化。初始化列表是按照类定义中成员的定义顺序来初始化的,而不是列表中的顺序。

3.拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。以下情况会调用拷贝构造函数:

  • 一个对象需要通过另外一个对象进行初始化

    比如用=进行赋值

  • 一个对象以值传递的方式传入函数体

    此时传入函数的对象会调用拷贝构造函数生成一个新的对象,并在函数执行完毕后析构掉它

  • 一个对象以值传递的方式从函数返回

也就是说当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用拷贝构造函数。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:

classname (const classname &obj) {
   // 构造函数的主体
}

4.友元函数(friend function)

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字friend,如下所示:

class Box{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

声明类ClassTwo的所有成员函数作为类ClassOne的友元,需要在类ClassOne的定义中放置如下声明:

friend class ClassTwo;

也可以将类A中的一个成员函数声明为B的friend,但这样做要严格控制好定义的顺序,首先定义类A,然后声明成员方法foo(此时不能定义foo,因为若要成为类B的friend,就可能获取类B的成员,而此时B还未被定义),接着定义类B,声明A中的方法foo为friend:friend void A::foo(),最后再定义foo,就可以在foo中获取类B的成员了。

友元函数没有this指针,因为友元不是类的成员,只有成员函数才有this指针。派生类的友元函数可以访问从基类继承来的所有成员,但是对于基类来说,它并不是友元函数。

如果类中某个成员函数若想调用这个函数,则该函数的定义必须在(类外部的)友元函数声明之后,即使类中声明友元函数时已经提供了定义,这是因为类的友元函数声明并不是通常意义上的声明,也就是说就算在一个类的定义中定义了一个函数为友元,在类的外部还需要该函数的一次声明。

5.内联函数(inline function)

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数行数较多,编译器会忽略 inline 限定符。

  • 在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符
  • 内联函数的定义必须出现在内联函数第一次调用之前
  • 尽量当函数只有10行甚至更少时才将其定义为内联函数

6.类的静态成员(static)

首先介绍普通函数中的静态变量:

When a variable is declared as static, space for it gets allocated for the lifetime of the program. Even if the function is called multiple times, space for the static variable is allocated only once and the value of variable in the previous call gets carried through the next function call.

也就是说,一旦函数中静态变量声明并赋值后,之后所做的任何改动都会持续到整个程序的生命周期最后,不论这个函数被调用了多少次。

而如果全局变量被定义为静态,则只能在定义的文件中使用,不能被其他文件使用,静态函数也一样,除非被定义在头文件中,并被其他文件包含。静态的局部变量被储存于静态区,生命周期为整个程序的运行周期,普通局部变量定义在栈中,使用(函数调用)完毕后就会被销毁。

当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本,因此静态成员不能在构造函数中初始化。静态成员在类的所有对象中是共享的。我们通常不能在类中去定义和初始化一个静态成员,而要在类的外部来定义和初始化静态数据成员,除了通过使用范围解析运算符::,还要写上成员的类型,但不需要加static。和其他对象一样,静态数据成员也只能被定义一次。当静态数据为constexpr时,可以在类内部初始化,注意仅仅是初始化,在外部仍需提供一次定义,且定义时不需要(也不能)提供初始化值。静态成员不能作为函数的默认参数。

我们同样可以将一个类的对象声明为静态的,那么这个对象会在这个程序的生命周期中一直存在。

静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数可以在类对象不存在的情况下被调用,只要使用类名加范围解析运算符::就可以。

  • 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数
  • 静态成员函数没有this指针
  • 只用在类中的声明加上static,外部的定义不需要

7.多态

多态(polymorphism)指为不同数据类型的实体提供统一的接口。程序执行时,相同的讯息可能会送给多个不同类别的物件,而系统可依据物件所属类别,引发对应类别的方法,而有不同的行为。

虚函数

虚函数是在基类中使用关键字virtual声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定

纯虚函数

我们可以在基类中只定义虚函数而不给出具体的实现,而在不同的派生类中才去实现,这时就会用到纯虚函数。

virtual int func() = 0;

=0告诉编译器,这个函数是纯虚函数,没有函数体。

另外父类的虚函数或纯虚函数在子类中依然是虚函数,有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字final来避免该函数再次被重写。

不能声明为虚函数的函数

  1. 构造函数,构造函数在生成对象之前就被调用,而虚函数需要调用对象内的虚函数表。

  2. 内联函数

  3. 静态成员函数,它和对象是分离的

8.const成员函数

要声明一个const类型的类成员函数,只需要在成员函数参数列表后加上关键字const,如:

int get() const;

若将成员函数声明为const,相当于对象所含有的隐式指针this指向的是const对象,则:

  1. 该函数不允许修改类的任何数据成员,不管其是否具有const性质

  2. 只能访问const函数

  3. const可以作为重载标识符

注意const对象只能访问const成员函数,也不能修改任何成员变量,除非变量被mutable修饰。const成员函数的本质在于,将隐式的this指针从指向nonconst对象的const指针变成指向const对象的const指针,nonconst的this指针无法和一个const对象绑定,反过来也说明const对象无法调用非const成员函数。

在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const成员函数。

补充:

  • const对象在文件中是local存在的,也就是说默认每个const对象在不同文件中是独立的,如果要使不同文件使用同一个const对象,则必须在定义的时候使用extern关键字。
  • const引用可以初始化为与其不同的类型,前提是这两种类型是可以转换的,只有const引用可以允许这种初始化是因为编译器实际上为其创建了一个initializer类型的临时const对象,然后再将引用与该临时对象绑定,也就是说绑定的对象其实并不是初始化表达式右边的那个对象,所以当然不能改动它了。

9.this指针

this指针是成员函数中隐性定义的指向当前对象的指针,比如调用obj.foo(),相当于从函数的一个参数传入了&obj为this。通过将成员函数的返回类型定义为返回引用,并在函数体中返回*this,可以达到把函数调用的结果变成左值的目的,因为返回的是调用该成员函数的对象本身,而不是它的拷贝。

成员函数是const的情况下,this指向的是一个const对象,因此要返回*this的话,函数返回类型也要是const引用才行。

static成员方法没有this指针,因为它不随实例而存在,也不被实例调用,从这点可以推断出static成员方法不能被声明为const,也不能调用有this指针的其他成员函数。但实例依旧可以正常调用static成员函数。

10.移动(move)

在一些情况下被拷贝的实例马上就被销毁,为了提高效率,C++提供了可以移动的新特性,即把对象从一部分内存移到另一部分内存,免去创建拷贝的麻烦。通过在类中定义“移动构造函数”和“移动赋值操作符”,


Comments

Content