Fork me on GitHub

C++ 对象创建

5. Constructor(构造函数)

  • If a class has a constructor, the compiler automatically calls that constructor at the point an object is created, before client programmers can get their hands on the object.
  • The name of the constructor is the same as the name of the class.

5.1 Destructor(析构函数)

  • In C++, cleanup is as important as initialization and is therefore guaranteed with the destructor.

  • The destructor is named after the name of the class with a leading tilde(~). The destructor never has any arguments.

  • The destructor is called automatically by the compiler when the object goes out of scope.

  • constructor.cpp

#include <stdio.h>

class A 
{
public:
    int i;
    A();
    ~A();
    void f();
};

// 构造函数
A::A()
{
    i = 10;
    printf("A::A() -- this = %p\n", this);
}

// 析构函数
A::~A()
{
    printf("A::~A() destructor...");
}

void A::f()
{
    i = 20;
    printf("A::f() -- &i = %p\n", &i);
    printf("A::f() -- this = %p\n", this);
}

int main()
{
    A a;
    a.f();
    return 0;
}
  • 结果输出:

6. 对象初始化

6.1 内存分配

  • The compiler allocates all the storage for a scope at the opening brace of that scope.
  • The constructor call doesn't happen until the sequence point where the object is defined.
#include <stdio.h>

class A
{
public:
    // 对变量 i 的声明
    int i;
    A();
    void f();
};

A::A()
{
    printf("A::A() -- this = %p\n", this);
}

void A::f()
{
    i = 20;
    // 输出 i 的地址
    printf("A::f() -- &i = %p\n", &i);
    printf("A::f() -- this = %p\n", this);
}

int main()
{
    A a;
    a.i = 10;
    a.f();

    // 定义 aa 的时候,才会调用构造函数
    A aa;
    aa.f();
    return 0;
}
  • 结果输出:

  • 内存分配问题:nojump.cpp

class X {
public:
    X();
};

X::X() {}

void f(int i) {
    if (i < 10) {
        goto jump1;
    }
    X x1;
    jump1:
        switch(i) {
            case 1:
                X x2;
                break;
            case 2:
                X x3;
                break;
        }
}

int main()
{
    f(9);
    f(11);
}

6.2 默认构造函数

  • A default constructor is one that can be called with no arguments.
struct Y {
    float f;
    int i;
    Y(int a);
};
  • 对象初始化
// 正确:
Y y1[] = {Y(1), Y(2), Y(3)};

// 错误:
Y y2[2] = {Y(1)};
  • a.cpp
#include <stdio.h>

class A
{
public:
    // 对变量 i 的声明
    int i;
    A(int a);
};

A::A(int a)
{
    i = a;
    printf("A::A() -- this = %p\n", this);
}

int main()
{
    // 编译出错,由于没有默认构造函数
    A y[2] = {A(1)};
    return 0;
}

6.3 动态分配内存

  • new is the way to allocate memory as a program runs. Pointers become the only access to that memory.
  • delete enables you to return memory to the memory pool when you are finished with it.
// new
new int;
new Stash;
new int[10];


// delete
delete p;
delete[] p;

// 示例
int *psome = new int[10];
// The new operator returns the address of the first element of the block.


delete[] psome;
// The presence of the brackets tells the program that it should free the whole array, not just the element.
  • new_delete.cpp
#include <iostream>
using namespace std;

class A
{
private:
    int i;
public:
    A() {
        i = 0;
        cout << "A::A() running~" << endl;
    }
    ~A(){
        cout << "A::~A(), i = " << i << endl;
    }
    void set(int i) {
        this->i = i;
    }
    void f() {
        cout << "f() running~" << endl;
    }
};

int main()
{
    A* p = new A[10];
    for (int i = 0; i < 10; i++) {
        p[i].set(i);
    }
    // 此时,只会调用一次析构函数
    // delete p;  
    // 此时,会调用 10 次析构函数
    delete[] p;

    return 0;
}

6.3.1 new 和 delete 建议

  • Don't use delete to free memory that new didn't allocate.
  • Don't use delete to free the same block of memory twice in succession.
  • Use delete[] if you used new [] to allocate an array.
  • Use delete(no brackets) if you used new to allocate a single entity.
  • It's safe to apply delete to the null pointer(nothing happens).

// 删除空指针

int main()
{
    int *p;
    delete p;

    return 0;
}

6.4 访问限制(private, public)

  • To keep the client programmer's hands off members they shouldn't touch.
  • To allow the library designer to change the internal workings of the structure without worrying about how it will affect the client programmer.
  • The members of a class can be cataloged(分类), marked as:
    • public
    • private
    • protected

6.4.1 public

  • public means all member declarations that follow are available to everyone.

6.4.2 private

  • The private keyword means that no one can access that member except inside function members of that type.

-d.cpp

#include <iostream>
using namespace std;

class A {
    private:
        int i;
        int *p;
    public:
        A() { p = 0; cout << "A::A()" << endl;}
        ~A(){ if (p) delete p; cout << "A::~A()" << i << endl;}
        void set(int ii) { i = ii; }
        void f() { p = new int; }
        // 指针可以获取到 私有的 i
        // private 是针对类,而不是针对对象
        // private 的限制,仅仅在编译时刻。
        // 同一个类的对象之间,是可以互相访问私有的成员变量
        void g(A* q) { cout << "A::g(), q->i = " << q->i << endl; }
};

int main()
{
    A* p = new A[10];
    for (int i = 0; i < 10; i++) {
        p[i].set(i);
    }
    A b;
    b.set(100);
    p[0].g(&b);
    delete[] p;

    return 0;
}

6.4.3 friends

  • To explicitly grant access to a function that isn't a member of the structure.

  • The class itself controls which code has access to its members.

  • Can declare a global function as a friend, as well as a member function of another class, or even an entire class, as a friend.

  • my_friend.cpp

// declaration (incomplete type specification)
struct X;

struct Y {
    void f(X*);
};

// definition
struct X {
private:
    int i;
public:
    void initialize();
    // global friend
    friend void g(X*, int);
    // struct member friend
    friend void Y::f(X*);
    // Entire struct is a friend
    friend struct Z;
    friend void h();
};

void X::initialize() {
    i = 0;
}

void g(X* x, int i) {
    x->i = i;
}

void Y::f(X* x) {
    x->i = 47;
}

struct Z {
    private:
        int j;
    public:
        void m();
};

int main()
{
    return 0;
}

6.4.4 class vs. struct

  • class defaults to private;
  • struct defaults to public.

6.5 初始化列表(initializer list)

  • Can initialize any type of data
    • pseudo-constructor calls for built-ins.
    • No need to perform assignment within body of constructor.
  • Order of initialization is order of declaration
    • Not the order in the list!
    • Destroyed in the reverse order.
class Point {
private:
    const float x, y;
    // 使用初始化列表,对 x, y 初始化
    Point(float xa = 0.0, float ya = 0.0) : y(ya), x(xa) {}
};

6.5.1 Initialization vs. assignment

// 示例一:仅做初始化
Student::Student(string s):name(s) {}
// initialization
// before constructor


// 示例二:先做初始化,再做赋值
Student::Student(string s) {name = s;}
// assignment
// inside constructor
// 由于先做初始化,name 必须提供一个默认构造函数


// 示例三:
#include <iostream>
using namespace std;

class B {
public:
    B(int i) {}
};

struct A {
    private:
        int i;
        int *p;
        B b;
    public:
        A():p(0) {
            // 此处,对 b 赋值之前,会使用默认构造函数,初始化 b
            // 由于没有默认构造函数 B::B(),从而无法初始化,导致编译失败
             b = 0; 
             cout << "A::A()" << endl;
        }
        void set(int ii) { i = ii; }
};

int main()
{
    A* p = new A[10];
    for (int i = 0; i < 10; i++) {
        p[i].set(i);
    }

    return 0;
}
  • 编译异常截图

参考资料:

posted @ 2022-05-11 18:35  小a的软件思考  阅读(74)  评论(0编辑  收藏  举报