LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

C++面向对象程序设计笔记:第二章 类和对象基础

2022/9/13 C++入门

C++面向对象程序设计笔记:第二章 类和对象基础

类和对象的基本概念

在类的定义中,用下列访问范围关键字来说明类成员可被访问的范围:

  • private: 私有成员,只能在成员函数内访问
  • public : 公有成员,可以在任何地方访问
  • protected: 保护成员
    注:这三种关键字出现的次数和先后次序没有限制。

如果某个成员前面没有上述关键字,则缺省地被认为是私有成员。

构造函数

成员函数的一种:

  • 名字与类名相同,可以有参数,不能有返回值(void也不行)
  • 作用是对对象进行初始化,如给成员变量赋初值
  • 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数(不做任何操作)。
  • 如果定义了构造函数,则编译器不生成默认的无参数的构造函数
  • 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
  • 一个类可以有多个构造函数,参数个数或类型不同

无构造函数:

class Complex {
private:
    double real, imag;
public:
    void Set(double r, double i);
}; // 编译器自动生成默认构造函数
Complex c1; // 默认构造函数被调用
Complex* pc = new Complex; // 默认构造函数被调用

有构造函数:

class Complex {
private:
    double real, imag;
public:
    Comple(double r, double i=0);
};
Complex::Complex(double r, double i) {
    read = r, imag = i;
}
Complex c1; // 编译错误,缺少构造函数的参数
Complex* pc = new Complex; // 编译错误,同上
Complex c1(2); // ok

构造函数在数组中的使用:

class CSample {
    int x;
public:
    CSample() {
        cout << "Constructor 1 Called" << endl;
    }
    CSample(int n) {
        x = n
        cout << "Constructor 2 Called" << endl;
    }
};
int main() {
    CSample array1[2]; // 无参构造Con1
    CSample array2[2] = {4, 5}; // 有参构造函数Con2
    CSample array3[2] = {3}; // 第一个有参Con2,第二个无参Con1
    CSample* array4 = new CSample[2]; // 无参Con1
    delete []array4;
    return 0;
}
class Test {
public:
    Test(int n){}           // (1)
    Test(int n, int m){}    // (2)
    Test(){}                // (3)
}
Test array1[3] = {1, Test(1, 2)}; // (1),(2),(3)
Test array2[3] = {Test(2, 3), Test(1, 2), 1}; // (2),(2),(1)
Test* pArray[3] = {new Test(4), new Test(1,2)}; // (1),(2),NULL

复制构造函数

只有一个参数,即对同类对象的引用。编译器会自动实现一个默认的。

X::X(X& x); X::X(const X& x);

class Complex {
private:
    double real, imag;
    /*
    Comple(const Complex& c) {
        // ...
    }
    */
}
Complex c1;     // 调用缺省无参构造函数
Complex c2(c1); // **调用缺省的复制构造函数**,将c2初始化成和c1一样。

使用的三种情况:

  1. 当用一个对象去初始化同类的另一个对象时

    Complex c2(c1);
    Complex c2 = c1; // **初始化语句**,非赋值语句
    
  2. 如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数被调用。

    void Func(A a1){ }
    int main() {
     A a2;
     Func(a2); // 会调用A的复制构造函数
     return 0;
    }
    
  3. 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:

    A Func() {
     A b(4);
     return b; // 调用复制构造函数
    }
    int main() {
     cout << Func().c;
     return 0;
    }
    

void fun(CMyclass obj_) { }
对于这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
可以考虑用CMyclass& 引用类型作为参数。
如果希望确保实参的值在函数中不应该改变,那么可以加上const关键字。

对象间的赋值并不导致复制构造函数被调用

注意初始化复制构造函数的区别!

class CMyclass {
public:
    int n;
    CMyclass() {}
    CMyclass(CMyclass& c) { n = 2 * c.n; }
}
int main() {
    CMyclass c1, c2;
    c1.n = 5;
    c2 = c1;        // 赋值,不调用
    CMyclass c3(c1);// 初始化,调用复制构造函数
    // c2.n == 5
    // c3.n == 10
}

类型转换构造函数

实现类型的自动转换。
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。

class Complex {
public:
    double real, imag;
    Complex(int i) { // 类型转换构造函数
        real = i; imag = 0;
    }
    Complex(double r, double i) {real = r, imag = i; }
};
int main() {
    Complex c1(7, 8);
    Complex c2 = 12;
    c1 = 9; // 9被自动转换成一个临时Complex对象
    return 0;
}

析构函数

  • 名字与类名相同,在前面加~,没有参数和返回值,一个类最多只能有一个析构函数。
  • 析构函数在对象消亡时被自动调用。可以定义析构函数来做对象消亡前的善后工作,比如释放分配的空间等。
  • 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
  • 如果定义了析构函数,则编译器不生成缺省析构函数。

对象数组生命周期结束时,对象数组的每个元素的析构函数都会被调用。

CMyclass obj;
CMyclass fun(CMyclass sobj) { // 参数对象消亡也会导致析构函数被调用
    return sobj;    // 函数调用返回时生成临时对象返回
}
int main() {
    obj = fun(obj); // 函数调用的返回值被用过后,该临时对象析构函数被调用
    return 0;   // 程序结束后,obj的析构函数被调用
}
// 总共调用了3次析构函数

Demo:

class Demo {
    int id;
public:
    Demo(int i) {
        id = i;
        cout << "id=" << id << " constructed\n";
    }
    ~Demo() {
        cout << "id=" << id << " constructed\n";
    }
};
Demo d1(1);
void Func() {
    static Demo d2(2);
    Demo d3(3);
    cout << "func\n";
}
int main() {
    Demo d4(4);
    d4 = 6;
    cout << "main\n";
    {
        Demo d5(5);
    }
    Func();
    cout << "main ends\n";
    return 0;
}
/*
输出:
id=1 constructed
id=4 constructed
id=6 constructed
id=6 constructed
main
id=5 constructed
id=5 constructed
id=2 constructed
id=3 constructed
func
id=3 constructed
main ends
id=6 constructed    // d4调用析构函数
id=2 constructed    
id=1 constructed    // 先初始化的后析构
*/

错题:
如下程序调用析构函数几次?
3次,p2不会消亡,因为指针只有在delete的时候才会消亡。

int main() {
    A* p = new A[2];
    A *p2 = new A;
    A a;
    delete[] p;
}

注:dev会出于优化目的,不生成返回值临时对象。

课后习题

005:编程填空:学生信息处理程序

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <cstdlib>
using namespace std;

class Student {
private:
char *name;
int age, number;
int socre1, socre2, socre3, socre4;
float res;
public:
    Student() {
        name = new char[20];
    }
    void input() {
        scanf("%[^,],%d,%d,%d,%d,%d,%d", name, &age, &number, &socre1, &socre2, &socre3,&socre4);
    }
    void calculate() {
        res = 1.0 * (socre1 + socre2 + socre3 + socre4) / 4.0;
    }
    void output() {
        if(int(res * 10) % 10 == 0) 
            printf("%s,%d,%d,%.0f", name, age, number, res);
        else
            printf("%s,%d,%d,%.1f", name, age, number, res);
    }
};

int main() {
    Student student;        // 定义类的对象
    student.input();        // 输入数据
    student.calculate();    // 计算平均成绩
    student.output();       // 输出数据
}

006:奇怪的类复制

#include <iostream>
using namespace std;
class Sample {
public:
    int v;
Sample(int v_ = 0) {
        v = v_;
    }
    Sample(const Sample& a) {
        v = a.v + 2;
    }
    void operator=(Sample& a) {
        v = 5;
    }
};
void PrintAndDouble(Sample o)
{
    cout << o.v;
    cout << endl;
}
int main()
{
    Sample a(5);
    Sample b = a;
    PrintAndDouble(b);
    Sample c = 20;
    PrintAndDouble(c);
    Sample d;
    d = a;
    cout << d.v;
    return 0;
}

008:超简单的复数类

#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
class Complex {
private:
    double r,i;
public:
    void Print() {
        cout << r << "+" << i << "i" << endl;
    }
Complex() {
        r = 0;
        i = 0;
    }
    Complex(char *s) {
        r = i = 0;
        int n = strlen(s);
        int pos;
        for(pos = 0; pos < n; pos++) {
            if(s[pos] == '+') {
                break;
            }
            r *= 10;
            r += s[pos] - '0';
        }
        pos++;
        for(; pos < n - 1; pos++) {
            i *= 10;
            i += s[pos] - '0';
        }
    }
};
int main() {
    Complex a;
    a = "3+4i"; a.Print();
    a = "5+6i"; a.Print();
    return 0;
}

009:哪来的输出

#include <iostream>
using namespace std;
class A {
    public:
        int i;
        A(int x) { i = x; }
~A() {
        cout << i << "\n";
    }
};
int main()
{
    A a(1);
    A * pa = new A(2);
    delete pa;
    return 0;
}
img_show