跳转至

C++ 笔记 | 第3课 类

类(class)

类是将数据和相应对这些数据的操作函数进行封装,并设置访问权限。class 不同于 structunionstructunion 是纯数据类型,但不包括函数(操作)

类是对象的抽象,而对象是类的实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板 (模子)。

类可以继承和派生,并引出了访问权限控制和调整、保护成员、成员函数名静态和动态束定(虚函数)等一系列问题。

C++
//类 举例
class Date
{
public:
    //可以把成员函数直接在类说明中定义,这样成员函数若符合条件,将自动成为内联函数
    void set(int d, int m, int y)
    { day = d; month = m; year = y; }
    int isLeapYear()
    { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);}
    void display()
    { cout << month << ":" << day << ":" << year << endl; }
private:
    int day, month, year;
};

构造函数(constructor)和析构函数(destructor)

类建立后往往需要初始化。

构造函数是与类同名的成员函数。

没有定义构造函数编译器会自动生成一个构造函数(公有构造函数)。

与类同名带前缀~的成员函数被称为是析构函数。

构造函数/析构函数不能在程序中显式地直接被调用,而是在对象创建和撤消时隐式地自动被调用。

构造函数可以多个同名(函数重载),以达到各种初始化的目的。

可以把构造函数改写成带缺省参数的函数:如 Myclass(int i = 0);

可以由缺省值构造函数代替转换构造函数

同名的构造函数必须在类的 public 声明中逐个列出——如果想定义对象而不赋初值,必须再加一个无参构造函数。(用 new 申请某一个类的动态对象数组时,在该类中必须能够匹配到没有形参的或缺省参数的构造函数)

构造函数/析构函数没有返回类型。

析构函数执行顺序与构造函数执行顺序相反,一一对应。

C++
class Circle
{
public:
    Circle(double x = 0, double y = 0, double r = 0) : x_(x), y_(y), r_(r) { cout << "Circle(double, double, double) called\n"; } // :x_(x) 的写法效率高一些
    ~Circle() { cout << "~Circle() called\n"; }

    void xSetValue(double x) { x_ = x; }
    void ySetValue(double y) { y_ = y; }
    void rSetValue(double r) { r_ = r; }

    void display() const
    {
        cout << "O(" << x_ << ',' << y_ << ")" << '\t' << "radius=" << r_ <<endl;
    }
    void getArea() const
    {
        cout << "area=" << 3.14 * r_ * r_ <<endl;
    }

private:
    double x_;
    double y_;
    double r_;
};

内联构造函数

可以通过说明时把构造函数体直接放在类内,使之成为内联构造函数。符合内联函数的限制条件的类的构造函数体或成员函数体放在类内,该函数将自动成为内联函数. 目的:提高执行效率.

在类外可以通过在类的成员函数前加 inline ,使之成为内联构造函数

C++
1
2
3
4
5
6
7
8
9
//类中声明
public:
    Myclass(int i = 0);

// 类外函数定义
inline Myclass::Myclass(int i) 
{ 
    cout << "Constructor called. " << i << endl; 
}

复制构造函数

复制(拷贝)构造函数是一种特殊的构造函数,其形参只有一个且是本类对象的引用。

作用是:使用一个已经存在的对象(由复制构造函数的参数指定),去构造并初始化同类的一个新对象。

C++
class Point
{
public:
    Point(int x = 0, int y = 0):x_(x),y_(y){}
    Point(Point &p):x_(p.x_),y_(p.y_){}
private:
    int x_, y_;
}
int main(){
    Point A(4,5);
    Point B(A);
}

浅复制与深复制

浅复制:复制指针时将地址直接复制,使得不同结构体/对象的指针指向一块内存

深复制:重新申请一块内存,再将原来的数据拷贝一份放在新申请的内存里。

在声明一个类时,如果不写复制构造函数,编译器会自动为此类生成一个复制构造函数,但这种自动生成的复制构造函数一般都是浅复制,只是把一个对象中的各个成员一一对应赋值给另一个对象的各个成员,包括指针成员的值。如果一个类中含有指针成员,这样会导致两个对象的指针成员指向同一块动态内存,往往会造成析构对象执行析构函数释放动态内存时,产生致命错误。因此含有指针成员的复制构造函数一般需要自己写.

常成员函数

不修改类中任何成员值的函数,可以定义为常成员函数。

在函数名 () 后加上修饰符 const ,可确保此函数中不会有任何修改类的成员的行为,否则编译时就会出现错误信息。

C++
1
2
3
4
void display() const
{
    cout << "O(" << x_ << ',' << y_ << ")" << '\t' << "radius=" << r_ << endl;
}

mutable关键字

在声明类中数据时使用 mutable 关键字,使得该数据在常成员函数中可以被修改。

如:使用 mutable int count; 在类中创建一个计数器变量。

静态成员

同一个类中的不同对象之间共享一个数据,可以用来传递信息。

C++
private:
    static int num;

区分:成员,常成员,静态成员,常静态成员参看CSDN | C++笔记 | 类数据成员 const static

友元

让不是本类成员函数的其它函数也能直接访问本类的私有成员方法是:在类内逐个列出其友元函数,每个函数说明前面加上关键字 friend 。每一个友元都必须在此类中声明,这有利于此类对访问权限的控制。

C++
/* 
编程实现:定义Boat与Car两个类,两者都有weight成员表示重量,
并为每个类设计构造函数(可赋初值也可以不赋初值),
设置新值函数Set, 打印成员值函数Print。
再定义两个类共同的一个友元函数TotalWeight(Boat B, Car C),计算B、C两者的重量之和。
 */
#include <iostream>
#include <stdlib.h>
using namespace std;
class Boat;
class Car;
void outputTotalWeight(const Boat &boat, const Car &car);

class Boat
{
public:
    Boat(double weight = 0) : weight_(weight) {}
    ~Boat() {}

    friend void outputTotalWeight(const Boat &boat, const Car &car);

    void weightSetValue(double weight) { weight_ = weight; }
    void display() { cout << "boat.weight=" << weight_ << endl; }

private:
    double weight_;
};
class Car
{
public:
    Car(double weight = 0) : weight_(weight) {}
    ~Car() {}

    friend void outputTotalWeight(const Boat &boat, const Car &car);

    void weightSetValue(double weight) { weight_ = weight; }
    void display() { cout << "car.weight=" << weight_ << endl; }

private:
    double weight_;
};

void outputTotalWeight(const Boat &boat, const Car &car)
{
    cout << "The total weight is " << boat.weight_ + car.weight_ << "." << endl;
}

int main()
{
    Boat boat(1);
    Car car(2);
    outputTotalWeight(boat, car);
    return 0;
}

嵌套类

C++
class enclose
{
public:
    class inner
    {
    public:
        void innerFunc();

    private:
        int innerData;
    };
    void encloseFunc();

private:
    int encloseData;
};

inner 被称为类 enclose 的嵌套类,受类 enclose 的作用域限制,另一个类中也可以嵌套一个同名的 inner 类,也可以直接定义一个同名的 inner 类,这些 inner 类相互之间既无影响也无任何关联。

现在要想使用 inner 类,必须加前缀 enclose:: 例如,在类外定义 inner 的成员函数,必须写成: void enclose::inner::innerFunc()

而想用 inner 类定义对象,必须写成: enclose::inner innerObject;

类的向前引用

与函数的向前引用类似(有些编译器不需要)

C++
1
2
3
4
5
class B;
class A
{ //... };//其中使用了类B
class B
{ //... };

this 指针

this 指针只能在成员函数内使用。

类的静态成员函数中无 this 指针。

this 指针不能被更新。

类的组合

类的数据成员是另一个类的对象(在已有的类基础上抽象实现更复杂的类) 例如类 class Triangle 的成员为三个 class Point 的对象

指向类的静态成员的指针

int *p = & Student::count;

可以直接通过指针访问公有静态数据成员

指向静态成员函数的函数指针

C++
1
2
3
4
5
//class中的public静态成员函数:
static void display(){cout...;}
//main()函数
void (*pFunc)() = Student::display;
pFunc(); // 相当于执行了display()这一函数

类与对象数组

C++
Student group[3] = {Student(17, "甲"), Student(18, "乙"), Student(19, "丙")}; //定义“三人行必有我师焉”小组

类与动态对象

C++
/*
构建一个球类Ball,其成员为球心坐标(x,y,z)和球半径r,
并设计构造函数(可以赋初值也可以不赋初值),复制构造函数,析构函数(打印信息,表示其被调用),
设置新值成员函数Set(), 取球心坐标成员函数GetX()、GetY()、GetZ(),
取球半径成员函数GetR(),打印成员值成员函数Print(),计算球体积成员函数Volume()。

并用此类分别定义一个长度为10的静态对象数组,一个长度为20的动态对象数组,
每个对象的球心坐标(x,y,z)和球半径r由随机数rand()产生(球半径r的值应该为正数),
打印每个球对象的(x,y,z)、r和体积;
然后分别将对象数组按照其r值从小到大排序,并打印排序后每个对象的(x,y,z)、r和体积;
并计算打印出所有对象的平均(x,y,z)、平均r和平均体积。
(并释放动态对象数组,注意观察析构顺序与构造顺序的差异。)

//随机数样例
#include <stdlib.h>
void main()
{
    int i, x, y, z, r;
    srand(0); // srand为随机数序列赋初值,可以随意给初值
    for (i = 0; i < 10; i++)
    {
        x = rand();
        y = rand();
        z = rand();
        r = abs(rand());
    }
}
 */

#include <iostream>
using namespace std;
class Ball
{
private:
    double x, y, z;
    double r;

public:
    Ball(double xx = 0, double yy = 0, double zz = 0, double rr = 0)
        : x(xx), y(yy), z(zz), r(rr) {}
    Ball(Ball &b) : x(b.x), y(b.y), z(b.z), r(b.r) {}
    ~Ball() { cout << "~Ball() called.\n"; }
    void xSet(double xx) { x = xx; }
    void ySet(double yy) { y = yy; }
    void zSet(double zz) { z = zz; }
    void rSet(double rr) { r = rr; }
    double xValue() { return x; }
    double yValue() { return y; }
    double zValue() { return z; }
    double rValue() { return r; }
    double volume() { return 4.0 / 3 * 3.1415926 * r * r * r; }
    void display()
    {
        cout << "center point (" << x << "," << y << "," << z << ")" << '\t'
             << "radius = " << r << '\t' << "volume = " << this->volume() << endl;
    }
};

/*选择排序——元素p[a]~p[b]升序排序*/
void selesort(Ball p[], int a, int b)
{
    int i, j, k;
    Ball d;                      // 用来交换的变量
    for (i = a; i <= b - 1; i++) // 对最后一个元素p[n-1]不用操作
    {
        k = i; // 记录现在到哪里了
        for (j = i + 1; j <= b; j++)
            if (p[j].rValue() < p[k].rValue()) // 比较的是半径
                k = j;                         // 从i的下一个开始找,如果有比i小元素(第j个)的就让k为j
                                               // 本质目的是找出i后面最小的一个
        if (k != i)                            // 如果i项不是最小的,那么换!
        {
            d = p[i]; // 排序的是元素
            p[i] = p[k];
            p[k] = d;
        }
    }
}

int main()
{
    int length = 0;
    Ball a[10];
    Ball *b = new Ball[20];
    double sumx = 0, sumy = 0, sumz = 0, sumr = 0, sumv = 0;

    // a
    sumx = sumy = sumz = sumr = sumv = 0;
    for (int i = 0; i <= 10 - 1; i++)
    {
        a[i].xSet(rand() / 100.0);
        sumx += a[i].xValue();
        a[i].ySet(rand() / 100.0);
        sumy += a[i].yValue();
        a[i].zSet(rand() / 100.0);
        sumz += a[i].zValue();
        a[i].rSet(abs(rand()) / 10000.0);
        sumr += a[i].rValue();
        sumv += a[i].volume();
        a[i].display();
    }
    selesort(a, 0, 9);
    for (int i = 0; i <= 10 - 1; i++)
        a[i].display();

    length = sizeof(a) / sizeof(Ball);
    cout << "Ball_A AVERAGE:" << endl;
    cout << "center point (" << sumx / length << "," << sumy / length << "," << sumz / length << ")" << '\t'
         << "radius = " << sumr / length << '\t' << "volume = " << sumv / length << endl;

    cout << endl;
    // b
    sumx = sumy = sumz = sumr = sumv = 0;
    for (int i = 0; i <= 20 - 1; i++)
    {
        b[i].xSet(rand() / 100.0);
        sumx += b[i].xValue();
        b[i].ySet(rand() / 100.0);
        sumy += b[i].yValue();
        b[i].zSet(rand() / 100.0);
        sumz += b[i].zValue();
        b[i].rSet(abs(rand()) / 10000.0);
        sumr += b[i].rValue();
        sumv += b[i].volume();
        b[i].display();
    }
    selesort(b, 0, 19);
    for (int i = 0; i <= 20 - 1; i++)
        b[i].display();

    length = 20;
    cout << "Ball_B AVERAGE:" << endl;
    cout << "center point (" << sumx / length << "," << sumy / length << "," << sumz / length << ")" << '\t'
         << "radius = " << sumr / length << '\t' << "volume = " << sumv / length << endl;

    // END
    delete[] b;
    return 0;
}