「BUAA-C++」Lec3:引用和拷贝构造


引用(reference)

  • 引用是一个更安全的指针(a safe pointer), 借值之名,行指针之实,实际上表示的是地址。

  • 引用必须初始化, 和C语言中野指针不同

    int b = 0;
    int &a; //error
    int &a = b;

  • 引用是常量,一旦定义,不可以再修改。与之不同,指针可以先后指向不同对象。

    int &a = b;
    a = c; //error

  • 引用不可以完全取代指针,例如在mallocnew中返回值只能是指针。

补充:内存分区问题

  • 代码区:储存我们写的代码,只读常量,无法修改。
  • 全局变量区(静态区):在程序运行开始,程序在内存中开辟一段区域,把这段区域全部置为0,用于存储全局变量。这也是就为什么全局变量不初始化值自动变为0。全局变量的初始化在main函数运行之前。
  • 运行内存区(runtime memory):包括栈区和堆区
    • 栈区:用于局部变量
      • 栈区内存是连续的
    • 堆区:用于动态内存分配(mallocnew时)。
      • 堆区内存是离散的,理论上来无限次使用malloc会导致程序崩溃
      • 应用场景:1.不能确定内存分配大小需要在运行时确定(例如链表 建立);2.需要人为控制变量的生命周期。

拷贝构造

C++的类默认支持用一个已存在的类初始化一个新建的类。

int main() {
    struct Student* a = new Student();
    struct Student b(*a);
}

C++中有拷贝有两种类型——

  • bitwise copy(浅拷贝)
  • logical copy(深拷贝)

拷贝构造时默认是浅拷贝,即把对象a所在内存空间中所有数据以字节的形式拷贝给新建的b对象。如果a对象中有指针成员,则只会把指针的值拷贝给b,这样会出现a,b两个对象的指针成员指向同一块内存。(编译时不报错,非常恐怖!!!)

class Student {

private:
    int a;
    int *b;
public:
    Student(int ai, int bi);
};
Student::Student(int ai, int bi) {
    this->a = ai;
    this->b = new int(bi);
}
int main() {
    Student mem1(1, 2);
    Student mem2(mem1);
    cout << mem1.b << "\n" << mem2.b << endl; //两者指针值值一样的,即值指向同一块内存
}

为了避免这种情况,我们需要自定义一个拷贝构造函数从而实现 深拷贝,主要写法为Object::Object(const Object &a) {},具体例子如下所示

class Student {
private:
    int a;
    int *b;
public:
    Student(int ai, int bi);
    Student(const Student & s);
};

Student::Student(const Student & s) {
    this->a = s.a;
    this->b = new int(*(s.b));
}

int main() {
    Student mem1(1, 2);
    Student mem2(mem1);
    cout << mem1.b << "\n" << mem2.b << endl; //这样两者指针值就是不一样的了
    func(mem1);
    return 0;
}

在C/C++中,“只要传值,就是一次拷贝构造”,当我们向一个函数传递实参,且这个函数的形参不是引用类型,此时程序会自动调用实参类型的拷贝构造函数。例如
class Student {
private:
    int a;
    int *b;
public:
    Student(int ai, int bi);
    Student(const Student & s);
};

Student::Student(const Student & s) {
    cout << "调用自定义拷贝构造函数" << endl;
    this->a = s.a;
    this->b = new int(*(s.b));
}

void func(Student s) {
	cout << "实参调用" << endl;
}

int main() {
	func(mem1);
	return 0;
}
/**
以上程序执行结果为——
调用自定义拷贝构造函数
实参调用
/

什么使用需要深拷贝???

  • 类构造函数中有new(说明有指针成员),一定要深拷贝!
  • 构造函数中没有new也不一定不需要深拷贝,有可能在其他位置出现new或者malloc

函数参数问题

  • Pass by value ?
  • Pass by address ?

传值和传地址在功能、性能和其他方面的比较如下——

Item 传值 传地址
功能 函数只能input,即只能使用实参的值,不能改变实参的值 函数既能input也能output,即不仅可以使用实参的值,也可以改变实参的值
性能 需要传递sizeof(value)大小的数据 只需要传递sizeof(int)大小的数据
其他 只要传值,就需要调用一次拷贝构造

结论:不要使用pass by value!!!


文章作者: Hyggge
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hyggge !
  目录