一些小细节
在c++中,struct的功能已经进行扩展,基本和class的功能一致
唯一的区别就在于struct默认为public,class默认为private
对象的创建
默认创建
如果不需要任何附加条件,仅仅是创建一个对象的话,直接写是OK的
对象创建时,如果是带括号的无参创建,实际意义是函数声明(很奇怪,在函数内部还能声明函数)
#include <iostream>
using std::cout; using std::endl; using std::string;
class Computer { public: void setbrand(const string& brand) { _brand = brand; } void setprice(const int& price) { _price = price; } private: string _brand; int _price; };
int main() { Computer p1; return 0; }
|
函数成员声明再定义
函数成员可以像外部函数一样声明后再在类外部进行定义
但此时会有一个缺点,我们想要通过函数成员对数据成员进行修改时,数据成员不允许访问,所以一般的,如果想要在类外部定义函数成员,就需要像namespace的引用方法一样去定义
#include <iostream>
using std::cout; using std::endl; using std::string;
class Computer { public: void setbrand(const string& brand); void setprice(const int& price); private: string _brand; int _price; };
void Computer::setbrand(const string& brand) { _brand = brand; } void Computer::setprice(const int& price) { _price = price; }
int main() { Computer p1; return 0; }
|
对象初始化
构造函数
构造函数就是用来初始化数据成员的
形式:没有返回值,非void,函数名与类名必须相同
默认构造函数
由系统提供的一个构造函数,因此即使对象的创建过程没有构造函数也是允许的
构造函数定义
#include <iostream>
using std::cout; using std::endl; using std::string;
class Computer { public:
Computer(const string& brand, const int& price) { _brand = brand; _price = price;
cout << "Have done structure!" << endl; } private: string _brand; int _price; };
void test0() { Computer p1("HUAWEI", 4999); } int main() { test0();
return 0; }
|
上述方法只是一般构造函数写法,赋值并非是真正对数据成员初始化
构造函数初始化对象
对于类中数据成员的初始化,需要初始化列表进行完成
形式:在构造函数的头与构造函数的体之间,用 ‘ : ‘ 进行分隔
Computer(const string& brand, const int& price) :_brand(brand) , _price(price) { cout << "Have done structure!" << endl; }
|
初始化数据成员的顺序与初始化列表无关,如果有两个数据成员ix和iy,当已经把ix初始化了后,用ix去初始化iy,iy的值仍为随机值
对象的销毁
析构函数
用于对象的销毁,系统会默认提供
形式:同构造函数一样,没有返回值
没有参数,函数名与类名相同,但前面带个 ‘~’,且析构函数只能有一个
一般形式
#include <iostream>
using std::cout; using std::endl; using std::string;
class Computer { public: Computer(const string& brand, const int& price) :_brand(brand) , _price(price) { cout << "Have done structure!" << endl; } ~Computer() { cout << "Have done Delete" << endl; } private: string _brand; int _price; };
void test0() { Computer p1("HUAWEI", 4999); p1.~Computer();
} int main() { test0();
return 0; }
|
输出结果
Have done structure! Have done Delete Have done Delete
|
可以看到输出了两次销毁,因此可以断定对象的销毁一定调用了析构函数,但调用析构函数并不一定代表对象的销毁
不同类型对象销毁的次序
一般来说,对象的销毁时间与其生命周期有关
全局对象生命周期最长,在程序结束时才能销毁
静态对象在main函数结束时进行销毁
局部对象以及语句块中的变量在函数或代码块结束时进行销毁
堆对象在delete或者free时进行销毁,如果不写就会造成内存泄漏
#include <iostream>
using std::cout; using std::endl; using std::string;
class Computer { public: Computer(const string& brand, const int& price) :_brand(brand) , _price(price) { } ~Computer() { cout<<_brand<<' '<< "Have done Delete" << endl; } private: string _brand; int _price; };
Computer p4("All", 4);
void test0() { Computer p1("temp", 1); static Computer p3("static", 3); } int main() { test0();
Computer p2("main", 2); Computer* p5 = new Computer("new", 5);
delete p5;
return 0; }
|
输出结果
temp Have done Delete new Have done Delete main Have done Delete static Have done Delete All Have done Delete
|
析构函数的注意事项
在Computer类中, 我们用string定义了_brand数据成员
如果非要采用字符数组的形式进行定义,会出现不确定数组大小是否合适的问题
改用指针的形式,通过new或者malloc分配空间可以解决该类问题
#include <iostream> #include <cstring>
using std::cout; using std::endl; using std::string;
class Computer { public: Computer(const char* brand, const int& price) :_brand(new char[strlen(brand)+1]()) , _price(price) { strcpy(_brand, brand); } ~Computer() { cout<<_brand<<' '<< "Have done Delete" << endl; } private: char* _brand; int _price; };
|
此时又会出现新的问题,我们无法确定对象在销毁时,new分配的空间是否会被delete掉,通过内存检测工具发现是没有销毁掉的,因此我们需要对析构函数进行改进,而这一情况的讲解我会放在拷贝函数之后
对象的拷贝
如果未显示定义,系统会默认提供一个拷贝函数
类中定义的固定形式
class Point { public: Point(const Point& p) : _ix(p._ix) ,_iy(p._iy) { ; } private: int _ix; int _iy; }
|
对象拷贝时的两种情况
#include <iostream> #include <cstring>
using std::cout; using std::endl; using std::string;
class Computer { public: Computer(const char* brand, const int& price) :_brand(new char[strlen(brand)+1]()) , _price(price) { strcpy(_brand, brand); } ~Computer() { cout<<_brand<<' '<< "Have done Delete" << endl; }
void print() { cout << _brand << ' '; cout << _price << endl; } private: char* _brand; int _price; };
void point(Computer p) { p.print(); }
Computer retu() { Computer pt("xx", 11); return pt; } void test1() { Computer p1("1", 1); Computer p2 = p1; Computer p3(p1); Computer p4("2", 2); p4 = p1; point(p1); } int main() { test1();
return 0; }
|
通过直接赋值的方式进行拷贝是浅拷贝,可以通过对赋值运算符重构使之成为深拷贝,同时注意函数传参如果采用实参传形参而不是引用的形式,那么传参的拷贝是浅拷贝
Computer& operator = (const Computer& p) { Computer tmp("", 1); tmp._brand = new char[strlen(p._brand) + 1](); tmp._price = p._price; strcpy(tmp._brand, p._brand);
cout<<tmp._brand<<' '<<"Have done copy"<<endl;
return tmp; }
|
构造拷贝函数时的两个注意点
1.为什么要使用&
如果不去使用&符号,函数形式会变成Computer(const Computer p)
可以发现如果这么做,参数部分就是创建对象的形式,他会像一个栈递归一样无限创建下去,直到内存爆满
又因为进行了指针访问,可以认为形参对象就在类的内部,可以访问数据成员
使用指针也可以代替&
2.为什么需要const修饰
首先需要了解左值和右值的概念
右值:没有写入内存的数据,不能进行取地址访问
左值:以及写入内存的数据,可以进行取地址访问
值得注意的是,函数如果有返回值,那么他的返回值就是临时变量,这里的临时变量就是右值,无法取地址访问
而通过const修饰,可以绑定右值
int a1 = 1; int& a2 = a1; int& a3 = 1; const int& a4 = 1;
|
可以发现带有a3无法编译通过,去掉a3后,a1a2a4是可以编译通过的,因为1是右值,无法进行取地址访问,而通过const修饰绑定右值后就可以强行访问了
同理,函数返回值也是如此
class Computer { public: Computer(const char* brand, const int& price) :_brand(new char[strlen(brand)+1]()) , _price(price) { strcpy(_brand, brand); } ~Computer() { cout<<_brand<<' '<< "Have done Delete" << endl; } Computer(const Computer& p) :_brand(p._brand) ,_price(p._price) { cout<<_brand<<' '<<"Have done copy"<<endl; } void print() { cout << _brand << ' '; cout << _price << endl; } private: char* _brand; int _price; };
Computer retu() { Computer pt("xx", 11); return pt; } void test2() { Computer a1("1",1); Computer a2 = retu(); }
|
如果在拷贝函数定义过程中去掉了const修饰,a2是无法通过的,通过const绑定右值后即可进行
关于拷贝函数和析构函数的创建时机
前文在析构函数中提到,如果采用字符数组的形式以new分配空间, 在对象销毁时会发生内存泄漏
此时我们选择对析构函数进行改进
void release() { delete[] _brand; _brand = nullptr; } ~Computer() { release(); cout<< "Have done Delete" << endl; }
|
此时我们可以避免内存发生泄漏
我们通过sizeof查看类的大小时往往不是我们想要的结果,比如_brand为11个字节时,再加上int时,该类的大小实际却为16,这是因为一种对齐数的存在,在64位系统下类和结构体的大小必须为8的倍数,32位系统下必须为4的倍数
在拷贝函数中我们注意到,当时我们只是采用地址直接赋值的方式对新的对象复制
往往会发生以下情况
void test3() { Computer p1("HUAWEI", 4999); Computer p2 = p1; p1.~Computer(); p2.print(); }
|
此时我们发现在程序运行过程中发生崩溃,这是因为字符数组我们仅仅复制了地址过去,p1在销毁后,p2中brand的地址仍是p1的brand,brand的值为NULL,进行了非法访问
改进方法,在拷贝函数中,不再是复制地址,而是另开辟一个新的空间存放brand,通过strcpy将brand复制过去
Computer(const Computer& p) :_brand(new char[strlen(p._brand) + 1]()) ,_price(p._price) { strcpy(_brand, p._brand);
cout<<_brand<<' '<<"Have done copy"<<endl; }
|
全过程代码
#include <iostream> #include <cstring>
using std::cout; using std::endl; using std::string;
class Computer { public: Computer(const char* brand, const int& price) :_brand(new char[strlen(brand)+1]()) , _price(price) { strcpy(_brand, brand); } void release() { delete[] _brand; _brand = nullptr; } ~Computer() { if(_brand) release(); cout<< "Have done Delete" << endl; } Computer(const Computer& p) :_brand(new char[strlen(p._brand) + 1]()) , _price(p._price) { cout << _brand << ' ' << "Have done copy" << endl; } Computer& operator = (const Computer& p) { Computer tmp("", 1); tmp._brand = new char[strlen(p._brand) + 1](); tmp._price = p._price; strcpy(tmp._brand, p._brand);
cout<<tmp._brand<<' '<<"Have done copy"<<endl;
return tmp; } void print() { cout << _brand << ' '; cout << _price << endl; } private: char* _brand; int _price; };
Computer p44("All", 4);
void test0() { Computer p1("temp", 1); static Computer p3("static", 3); }
void point(Computer p) { p.print(); }
Computer retu() { Computer pt("xx", 11); return pt; } void test1() { Computer p1("1", 1); Computer p2 = p1; Computer p3(p1); Computer p4(p1); p4 = p1; point(p1); p4 = retu(); }
void test2() { Computer a1("1",1); Computer a2 = retu(); } void test3() { Computer p1("HUAWEI", 4999); Computer p2(p1); p1.~Computer(); p2.print(); } int main() { test0();
Computer p2("main", 2); Computer* p5 = new Computer("new", 5); delete p5;
test1();
test2();
test3();
return 0; }
|
对象的现代写法
深拷贝采用swap的优化方式,而浅拷贝不允许,解释:在函数实参传形参过程中,采用了浅拷贝,当前对象未被创建过,swap后临时变量地址未知,delete时会出现问题
#include <iostream> #include <cstring> #include <algorithm>
using std::cout; using std::endl; using std::string;
class Computer { public: Computer(const char* brand, const int& price) :_brand(new char[strlen(brand) + 1]()) , _price(price) { strcpy(_brand, brand); cout << "Have done structure!" << endl; } void release() { delete[] _brand; _brand = nullptr; } ~Computer() { if (_brand) release(); cout << "Have do ne Delete" << endl; }
void swap(Computer& p) { std::swap(this->_brand, p._brand); std::swap(this->_price, p._price); }
Computer(const Computer& p) :_brand(new char[strlen(p._brand) + 1]()) ,_price(p._price) { strcpy(_brand, p._brand); } Computer& operator = (const Computer& p) { if (this == &p) return *this;
Computer tmp(p._brand, p._price);
swap(tmp);
return *this; }
void print() { cout << _brand << ' '; cout << _price << endl; } private: char* _brand; int _price; };
Computer p44("All", 4);
void test0() { Computer p1("temp", 1); static Computer p3("static", 3); }
void print(Computer a) { a.print(); }
Computer retu() { Computer pt("xx", 11); return pt; } void test1() { Computer p1("1", 1);
Computer p2 = p1; Computer p3(p1); Computer p4(p1); p4 = p1; print(p1); p4 = retu(); }
void test2() { Computer a1("1", 1); Computer a2 = retu(); } void test3() { Computer p1("HUAWEI", 4999); Computer p2(p1); p1.~Computer(); p2.print(); } int main() {
Computer p2("main", 2); Computer* p5 = new Computer("new", 5); delete p5;
test1();
return 0; }
|