1、类和动态内存分配
1.1、静态成员初始化
在知识点(四)中的3.2使用static修饰变量,成为静态成员变量,使得所有对象共享变量,但值得注意的是变量类型不仅为static,还是const类型,此时可以在类中声明并初始化,但当没有const修饰时,不可在类声明中初始化,因为初始化是方法文件,而不是头文件
静态成员变量初始化方法:
头文件中
class Student
{
private:
char *m_name;
int len;
static int m_num;
public:
Student();
Student(const char *s);
~Student();
friend std::ostream & operator<<(std::ostream &os,const Student &stu);
};
cpp文件中
int Student::m_num = 0; 使用作用域解析运算法,但是不包含static
1.2、类的动态内存分配
上面的学生类中有一个char指针用来存储学生的姓名,若采用固定长度的数据会造成资源浪费,此时可以在构造函数中动态的分配内存来存储姓名,待该对象消失时,使用析构函数回收该部分内存即可,因此此时析构函数不可省略。
int Student::m_num = 26; 使用作用域解析运算法,但是不包含static
Student::Student()
{
m_name = new char[4];
strcpy_s(m_name,4 ,"C++");
m_num++;
cout << "default object created!" << endl;
}
Student::Student(const char *s)
{
len = strlen(s);
m_name = new char[len + 1];
strcpy_s(m_name, len + 1, s);
m_num++;
cout << m_num << ":\"" << m_name << "\" object created by Ordinary constructor" << endl;
}
Student::~Student()
{
cout << "\"" << m_name << "\" object deleted" << endl;
m_num--;
delete[]m_name;
}
std::ostream & operator<<(std::ostream &os, const Student &stu)
{
os << stu.m_name << endl;
return os;
}
int main()
{
Student stu;
Student fhl("xiaofang");
return 0;
}
1.3、类的动态内存分配常见错误
现象:
1、对象的值传递
2、对象的直接赋值
3、以值方式返回对象(非引用方式)
上述三种操作都会调用默认拷贝构造函数,生成一个拷贝对象,在对拷贝对象析构的时候会直接影响源对象的析构函数(因为析构函数中包含delete,即造成对同一片空间的二次delete)。
void callme(Student stu) 值传递
{
cout << stu;
}
int main()
{
Student stu;
Student fhl("xiaofang");
callme(fhl); 报错
Student fhy = stu; 报错
return 0;
}
解决方法一:自定义拷贝构造函数(复制构造函数)
了解:深拷贝和浅拷贝的区别
1、 拷贝构造函数原型:
Class_name(const Class_name & Type);
2、 何时调用拷贝构造函数:
对象间的赋值操作:Student fhl(fhy);
值传递对象:callme(fhl);
编译器生成临时对象时(值方式返回对象)
对已有对象的new操作
3、 默认的拷贝构造函数(浅拷贝)
生成一个拷贝对象,逐个复制每个非静态成员,因此也称为浅拷贝
4、 自定义拷贝构造函数(深拷贝):重新申请内存空间,避免两次delete同一块内存
复制的是源对象指针指向的数据,而不是单纯的复制指针,此时在析构delete时,会释放不同的内存空间
class Student
{
private:
char *m_name;
int len;
static int m_num;
public:
Student(); 默认构造函数
Student(const char *s); 普通构造函数
Student(const Student &stu); 拷贝构造函数
~Student();
friend std::ostream & operator<<(std::ostream &os,const Student &stu);
};
Student::Student(const Student &stu)
{
len = strlen(stu.m_name);
m_name = new char[len + 1];
strcpy_s(m_name, len + 1, stu.m_name);
m_num++;
cout << m_num << ":\"" << m_name << "\" object created by Copy constructor" << endl;
}
解决方法二:赋值运算符重载:不创建新对象
并不是所有的问题都归咎于复制构造函数,赋值运算符同样需要注意:
Student fhy = stu; 可能是先调用复制构造函数创建临时对象,然后通过默认赋值对成员逐个复制
但是我通过visual studio 2017实测发现,并不会调用
class Student
{
private:
char *m_name;
int len;
static int m_num;
public:
Student(); //默认构造函数
Student(const char *s); //普通构造函数
Student(const Student &stu); //拷贝构造函数
Student & operator=(const Student &stu); //赋值运算符重载
~Student();
friend std::ostream & operator<<(std::ostream &os,const Student &stu);
};
Student & Student::operator=(const Student &stu)
{
if (this == &stu) //防止对象的自我复制
return *this;
delete[]m_name; //释放=左边对象原有内存中的数据,不释放的会浪费此块内存,下次new分配的是一块新空间
len = stu.len;
m_name = new char[len + 1];
strcpy_s(m_name, len + 1, stu.m_name);
cout << "调用赋值构造函数!" << endl;
return *this;
}
总结:
在知识点(三)3.3中说:一般提供两个构造函数,一个析构函数,但是到现在需要加上一个:
一般提供三个构造函数(默认构造、普通构造、复制构造函数),一个析构函数
2、其他知识点
2.1、静态成员函数
前有静态成员,现有静态成员函数,在成员函数前加上static变成静态成员函数。
class Student
{
private:
char *m_name;
int len;
static int m_num;
public:
Student(); //默认构造函数
Student(const char *s); //普通构造函数
Student(const Student &stu); //拷贝构造函数
Student & operator=(const Student &stu); //赋值运算符重载
static int Number() {return m_num;} //静态成员函数
~Student();
friend std::ostream & operator<<(std::ostream &os,const Student &stu);
};
访问方法:
int count = Student::Number();
同静态成员变量一样,不属于某个对象,所有对象共享一个函数,因此只能访问静态成员变量
2.2、返回对象
1、 返回指向const对象的引用,不会调用复制构造函数
2、 返回非const对象的应用:赋值运算符重载(返回的是指向调用对象的引用)
3、 返回局部对象,不要使用引用方式返回,因为函数执行完毕,引用指向的对象将不存在,而直接返回对象会调用复制构造函数
总之
1、如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。
2、如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,它必须返回一个指向这种对象的引用。
3、有些方法和函数〈如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为其效率更高。
2.3、指向对象的指针
1、首先创建对象数组,使用对象指针指向数组成员,即对象指针
2、使用new为对象分配内存
int main()
{
Student stu[2] = {
Student("xiaofang"),
Student("xiaowu")
};
Student *first = &stu[0]; //对象指针
Student *second = &stu[1];
Student *third = new Student(stu[1]); //已有对象的new操作,将调用拷贝构造函数
cout << *first << *second << *third << endl; //<<运算符重载
delete third;
return 0;
}
2.4、对象和指针
1、 对象指针的初始化
Student *first = &stu[0]; //初始化为已有对象
Student *third = new Student(stu[1]); //指针初始化并指向创建的新对象
2、 new和构造函数的搭配
Student *temp = new Student; //调用默认构造函数
Student *temp = new Student("xiaofang"); //调用普通构造函数
Student *temp = new Student(stu[0]); //调用拷贝构造函数