对象指针
#include <iostream>
using namespace std;
class Point {
public:
Point(int x = 0, int y = 0) : x(x), y(y) { }
int getX() const { return this->x; }
int getY() const { return y; }
private:
int x, y;
};
int main() {
Point a(4, 5);
Point *p1 = &a; //定义对象指针,用a的地址初始化
cout << (*p1).getX() << endl;//用指针访问对象成员
cout << a->getY() << endl; //用对象名访问对象成员
return 0;
}
如上述代码中就定义了对象指针,通过对象指针来访问对象成员
常对象指针
#include <iostream>
using namespace std;
class Point {
public:
Point(int x = 0, int y = 0) : this->x(x), this->y(y) { }
int getX() { return this->x; }
int getY() { return this->y; }
private:
int x, y;
};
int main() {
Point a(4, 5); //普通对象
Point const b(6,7); //常对象
Point const* pa = &a; //定义常对象指针,用a的地址初始化,a是普通对象,但不能通过常指针对象对其进行修改
Point* pb= & b; //定义常对象指针,用常对象b的地址初始化(所以该句代码会出错)
const Point & rb= b; //定义常引用rb,常对象b的别名,要用常引用来引用常对象,否则报错
cout << pa->getX() << endl;//用指针访问对象成员,该代码会报错,因为pa为常指针,要调用常函数
cout<< a->getY() <<endl;
cout << (*pb)->getY() << endl; //用指针求内容运算符访问对象成员
cout << rb.getX() << endl; //用常引用来访问对象成员
return 0;
}
常对象指针的内容均写到了上述代码中了
new 和 delete 操作符:
new, delete与C语言中的malloc和free做对比,malloc和free不会执行构造函数和析构函数。
注意:上述代码中动态分配和删除对象和对象数组的方式是不一样的
特殊成员函数
析构函数
#include <iostream>
using namespace std;
class Point {
public:
Point() : x(0), y(0) {
cout<<"Default Constructor called."<<endl;
}
Point(int x, int y) : x(x), y(y) {
cout<< "Constructor called."<<endl;
}
~Point() { cout<<"Destructor called."<<endl; } //析构函数
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
}
private:
int x, y;
};
int main() {
cout << "Step one: " << endl;
Point *ptr1 = new Point; //调用默认构造函数
cout<<ptr1->getX()<<endl; //输出GetX
delete ptr1; //删除对象,自动调用析构函数
cout << "Step two: " << endl;
ptr1 = new Point(1,2);
cout<<ptr1->getX()<<endl; //输出GetX
delete ptr1;
return 0;
}
在上述代码中指明了析构函数,析构函数用于在构造对象生存周期结束时自动释放资源。delete对象时自动调用析构函数。
默认构造函数:
如果程序员自己不定义一个构造函数,程序会提供一个默认构造函数,构造一个数值为空的对象。
拷贝构造函数:
如下图所示:点对象p3是用点对象p2创建的,这里调用了拷贝构造函数
在程序员没有定义拷贝构造函数时,编译器会提供一个默认拷贝构造函数(只能实现数值拷贝),当程序员定义了拷贝构造函数时,编译器会使用程序员定义的拷贝构造函数。
下图是一个点对象的拷贝构造函数。
赋值函数:
如果程序员没有重载操作符=,那么编译器会定义一个默认赋值函数;如果程序员定义了则会优先使用程序员定义的赋值函数。
深拷贝和浅拷贝
浅拷贝
只复制了指针的值,两个指针还是指向同一片内存空间,这种情况下,当使用析构函数回收两个指针资源时,会出现回收同一片空间两次的现象,这是要避免的;另外一个问题是在逻辑上我们是希望构造两个不同的对象的,但此时两个对象使用了同一片内存空间,这就导致修改其中一个对象时很可能会同时修改另一个对象。
默认拷贝函数时浅拷贝!
深拷贝
深拷贝会连同指针指向的那片空间一起拷贝,将指向的内容开辟一个新的空间存放。
动态数组类的实现
下面用一个动态数组类的例子来说明构造函数,析构函数的使用。
#include <iostream>
#include <stdlib.h>
using namespace std;
template <class T>
class DynamicArray{
private:
T* array;
unsigned int mallocSize;
public:
DynamicArray(unsigned length, const T &content){
mallocSize = length;
array = new T[length];
int i;
for(i = 0; i < length; i++){
array[i] = content;
}
cout<<endl<< "new T["<<this->mallocSize<<"] malloc "<< this->mallocSize << "*"<<sizeof(T)<<"="<<this->mallocSize *sizeof(T)<<" bytes memory in heap";
}
~DynamicArray(){
delete[] array;
cout<<endl<< "delete[] array free "<< this->mallocSize << "*"<<sizeof(T)<<"="<<this->mallocSize *sizeof(T)<<" bytes memory in heap";
}
unsigned int capacity() const;
T& operator[](unsigned int i);
};
template <class T>
unsigned int DynamicArray<T>::capacity() const {
return this->mallocSize;
}
template <class T>
T& DynamicArray<T>::operator[](unsigned int i){
if(i < 0 || i >= mallocSize){
exit(0);
}
return array[i];
}
在构造动态数组时要注意两个问题:
- 内存泄漏:如果进程未被杀死(进程被杀死后,内存会由操作系统自动回收),没有使用析构函数回收在堆区开辟的内存,那么在程序运行中程序所占内存就会越来越大,最终导致程序崩溃,这就是内存泄漏。
- 数组访问越界:这个问题在使用静态数组时也会出现,就是用下标访问动态数组时,下标范围超过数组有效范围,这时程序就会访问到内存中程序不该访问到的区域,产生安全隐患。上述程序在下标运算符重载时设置了边界保护,防止这种情况发生。
拷贝构造函数
一个对于深拷贝与浅拷贝的实战:
//在类中该成员函数已声明,这里只展示实现
template <class T>
DynamicArray<T>::DynamicArray(const DynamicArray<T> &x){
this->mallocSize = x.mallocSize;
this->array = new T[this->mallocSize];
int i;
for(i = 0; i < this->mallocSize; i++){
this->array[i] = x.array[i];
}
cout<<endl<< "Copy Constructor is called";
}
使用这句代码将可以实现拷贝构造