类和动态内存分配

2022/4/4 7:19:38

本文主要是介绍类和动态内存分配,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.静态类成员 P349

静态类成员的特点:无论创建了多少对象,程序都只创建一个静态类变量的副本。即类的所有对象共享同一个静态成员。

不能在类声明中初始化静态成员变量;类的静态成员必须在类内声明,在类声明之外使用单独的语句来进行初始化,且在类外初始化时使用作用域运算符,但不使用关键字static。

但是类的静态const成员或枚举类型可以在类声明中初始化。P303

如:

class A
{  
private:
    static int count ; // 类内声明
};
...
int A::count = 0 ; // 类外初始化,不必再加static关键字

但类的静态const成员或枚举类型可以在类声明中初始化:

class Bakery{
private:
    enum {Months = 12};
    double costs[Months];
    ...
};
class Bakery{
private:
    static const int Months = 12;
    double costs[Months];
    ...
};

一篇解释如下:

https://blog.csdn.net/jiayi_yao/article/details/50998765

 

2.复制构造函数(拷贝构造函数)与类的默认的重载赋值运算符 P353

参考:

https://blog.csdn.net/hui2702/article/details/106089097

https://blog.csdn.net/xiaozhidian/article/details/114377907

 

复制构造函数的原型:

Class_name(const Class_name &);

如:

Student(const Student &);//复制构造函数(拷贝构造函数)

一)何时调用:

复制构造函数和默认的重载赋值运算符的最大区别即是赋值运算符没有新的对象生成,而拷贝构造函数会生成新的对象。

调用复制构造函数的场景:

1)对象通过另外一个对象进行初始化
2)对象作为函数的参数,以值传递的方式传给函数。
3)当对象以值传递的方式从函数返回, 且接受返回值的对象没有初始化

调用默认的重载的赋值运算符的场景:P356

1)对象以值传递方式从函数返回,且接受返回值的对象已经初始化过
2)对象直接赋值给另一个对象,且接受值的对象已经初始化过

二)默认复制构造函数和类的默认赋值重载运算符的功能:

默认的赋值构造函数逐个赋值非静态成员(成员赋值也称浅复制,只复制指针值 p353,p355),复制的是成员的值;如果类成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。静态成员不受影响,因为它们属于整个类,而不是各个对象。

与默认的复制构造函数类似,默认的重载赋值运算符的实现也对成员进行逐个赋值;如果类成员本身就是类对象,则使用这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。

三)构造函数中使用new关键字的类,应: P375

1)若析构函数通过对指针类成员使用delete来释放内存,则每个构造函数都应该使用new来初始化指针,或将它设置为空指针nullptr(因为delete(无论是带[]或者不带[]的)都可以用于空指针。p364)

2)构造函数中要么使用new[],要么使用new,不能混用。如果构造函数使用new[],则析构函数使用delete[];如果构造函数使用new,则析构函数使用delete。

3)应定义一个复制构造函数,通过深度复制(即复制指针指向的数据,而不是只复制指针值 p355)将一个对象初始化为另一个对象。p364

该复制构造函数应该与下面类似:

String::String(const String & st)
{
    num_strings++;
    len = st.len;
    str = new char [len + 1];
    std::strcpy(str, st.str);//深拷贝,而不是使用str = st.str,这样会使str 和 st.str指向同一个字符串,在调用它们的析构函数时会释放同一个字符串
}

4)应定义一个重载赋值运算符,通过深度复制将一个对象复制给另一个对象。 p364

该重载的赋值运算符应该与下面类似:

String & String::operator=(const String & st)
{
    if(this == &st)
        return this;
    delete [] str;//str是类的私有成员对象,用于存储指向字符串的指针
    len = st.len;
    str = new char [len + 1];
    std::strcpy(str, st.str);//深拷贝,而不是使用str = st.str,这样会使str 和 st.str指向同一个字符串,在调用它们的析构函数时会释放同一个字符串
    return * this;
}

 

三)中关于重新定义复制构造函数和重载赋值运算符的原因:p355

当类中包含了使用new初始化的指针成员时,若将一个类对象a赋值(使用复制构造函数或者重载的赋值运算符)给对象b时,会调用默认的复制构造函数或者重载的赋值运算符;此举会导致对象a、b指向同一个存放在new申请的动态内存中的字符串(数据),而当对象过期,调用a、b的析构函数时,两个析构函数会将该动态空间中的字符串(数据)释放两次,从而引发错误。

三)中关于delete的问题:p358
析构函数中若包含 delete [] str; 则构造函数中应使用 new []初始化指针,或使用空指针初始化指针;因为delete [] 和使用 new []初始化的指针和空指针 nullptr 初始化的指针都兼容。因此若析构函数中使用 delete [],则构造函数中应该使用

String::String()
{
    len = 0;
    str = new char[1];
    str[0] = '\0';
}

而不是

String::String()
{
    len = 0;
    str = new char;
    str[0] = '\0';
}

 

3.静态类成员函数 p360

静态类成员函数的函数声明必须包含关键字static,若函数定义是独立的,则函数定义中不包含关键字static。

静态类成员函数不能通过对象调用(且静态类成员函数不能使用this指针,因为this指针指向调用类成员函数的对象);

如果静态成员函数是在类的公有部分声明的,则可以使用类名和作用域解析运算符来调用它;

//若String类有一个公有的静态成员函数HowMany()
class String{
...
public:
    static int HowMany(){return num_strings;}
};
...
int main()
{
...
int count = String::HowMany();//调用类的公有静态成员函数
...
}

 

4.指向对象的指针

1)使用new初始化对象时,若Class_name是类,value的类型为Type_name,则

Class_name * p = new Class_name(value);

将调用如下构造函数:

Class_name(Type_name &);

下列语句:

Class_name * ptr = new Class_name;

将调用默认构造函数。

 

5.再谈定位new运算符 p371

将定位new运算符用于对象

 

6.



这篇关于类和动态内存分配的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程