【C++笔记】09 类

 

1 访问标号(public/private/protect)

访问标号public、private、protect可以多次出现在类定义中,给定的访问标号应用到下一个访问标号出现时为止。

struct默认的访问标号为public,class默认的访问标号是private。

类对其成员的访问形式主要有以下两种:

  • 内部访问:由类中的成员函数对类的成员的访问;
  • 对象访问:在类外部,通过类的对象对类的成员的访问。

注:类的成员函数(内部访问)以及友元函数可以访问类中所有成员,但是在类外通过类的对象(对象访问)只能访问该类的公有成员。

2 类的成员

2.1 类的成员函数

在类中声明成员函数是必需的,而定义成员函数则是可选的。

注:在类内部定义的函数默认为inline(内联函数)。

调用成员函数时,实际上是使用对象来调用的。每个成员函数(除static成员函数外)都有一个额外的、隐含的形参this在调用成员函数时,形参this初始化为调用函数的对象的地址

2.2 构造函数

构造函数和类同名,且没有返回类型。一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。

如果没有为一个类显式地定义任何构造函数,编译器将自动为这个类生成默认构造函数(默认构造函数是不带参数的构造函数,或所有的形参都有默认实参的构造函数)。

如有类NoDefault,只有一个接受string实参的构造函数,则编译器将不会生成默认构造函数

class NoDefault
{
    NoDefault(const string&);
    ...
}

这就意味着:

  1. 具有NoDefault成员的每个类的每个构造函数,必须在成员初始化列表中通过传递一个初始的string值给NoDefault构造函数来显式地初始化NoDefault成员;
  2. 编译器不会为NoDefault类合成默认构造函数,如果希望提供默认构造函数,必须显式地定义,并且默认构造函数必须显式地初始化其NoDefault成员;
  3. NoDefault类型不能用作动态分配数组的元素类型;
  4. NoDefault类型的静态分配数组必须为每个元素提供一个显式的初始化式;
  5. 如果有一个保存NoDefault对象的容器,例如vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。

成员变量初始化

C++中成员变量的初始化顺序与变量在类型中的声明顺序相同,而与他们在构造函数的初始化列表中的顺序无关。

如:

class A
{
public:
    A(): j(0), i(j+2){}
    void print()
    {
        std::cout << "i: " << i;
	}
private:
    int i;
    int j;
}

int main()
{
    A a;
    a.print();
    return 0;
}

则输出的i值为一个内存中的垃圾数字。

从概念上讲,可以认为构造函数分两个阶段执行

  • 初始化阶段;

  • 普通的计算阶段,计算阶段由构造函数函数体中的所有语句组成。

初始化发生在计算阶段开始之前,计算阶段是赋值操作。

不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化(调用默认构造函数初始化,若没有构造函数,则编译错误),内置(int等)和复合类型(数组、指针等)的成员,只对定义在全局作用域的对象才初始化(初始化为0),定义在局部作用域中的对象包含的内置和复合类型的成员没有初始化。

由于内置类型的成员不进行隐式初始化,所以对这些成员进行初始化还是赋值无关紧要。但对于类类型的数据成员,若未在初始化列表显式初始化,而是在函数体内赋值,则相当于先调用类的默认构造函数进行初始化,再在函数体中赋值,故相比于直接利用初始化列表,效率较低。

有些数据成员必须在构造函数初始化列表中进行初始化。对de于这样的成员,在构造函数体内对它的赋值是不起作用的:

  • 没有默认构造函数的类类型的成员
  • const类型的成员变量
  • 引用类型的成员变量

拷贝构造函数(复制构造函数):

T(const T&);

拷贝构造函数、赋值操作符、析构函数总称为复制控制。如果类需要析构函数,则它也需要赋值操作符和拷贝构造函数(三法则)。

拷贝构造函数可用于:

  1. 根据另一个同类型的对象初始化一个对象;

  2. 复制一个对象,将它作为实参传给一个函数或从函数返回时复制一个对象(当形参或返回值为类类型时,将由拷贝构造函数进行复制);

  3. 初始化顺序容器中的元素;

    如:

    std::vector<std::string> s[5];
    

    编译器首先使用string默认构造函数创建一个临时值来初始化s,然后使用拷贝构造函数将临时值复制到s中每个元素。

  4. 根据元素初始化式列表初始化数组元素。

    如:

    T s[] = {
      string("aaa"),
      string("bbb"),
      string("ccc")
    };
    

拷贝构造函数的参数为一个引用,原因在于,如果不是引用,则相当于采用了值传递,而值传递会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。

深拷贝与浅拷贝

  • 浅拷贝:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其它对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。