C++中定义一个类,本质上就是定义一个数据结构,以关键字class作为标志
一个典型的类结构定义如下:
class test
{
public:
int key1;
int key2;
void get_sum();
};
关键字class与struct不同的地方在于,struct默认第一个访问说明符之前的成员是public的,而class则使这些成员为private权限的。
C的struct和C++struct不同:c++struct可以有成员函数
定义类的时候,可以定义成员函数,可以在类里面声明并定义函数体,也可以在类外面使用范围解析运算符::进行说明,例如上述的成员函数可以说明如下:
void test::get_sum()
{
return key1+key2;
}
定义类之后,就可以进行对象的声明以及访问了,如下:
test s;
s.key1=1;
s.key2=2;
cout<<s.get_sum();
(1)不要显式调用一个类的构造函数
(2)堆栈中声明结果是一个对象(.调用成员数据或者函数),在堆中声明返回的是一个指针(*().调用,等于->)
(3)堆栈中创建对象的时候,如果调用默认构造函数不需要(),使用后会当成一个函数声明;
但是,在堆中创建的时候,需要带上括号;
(4)显式的默认构造函数:
class_name() {} 或者 class_name()=default;
(5)复制构造函数
使创建的对象是另一对象的精确副本,如果未编写,编译时会自动生成,使用的是对象的const引用,如下:
class class_name
{
public:
class_name(const class_name& class1);
}
(6)初始化列表构造函数 #include<initializer_list>
形如:class_name(initializer_list<T> args) {...}
例如:std::vector<std::string> myVec ={"String 1","String 2","String 3"};
以下几种情况时必须使用初始化列表:
常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
(7)复制和赋值的区别
在对象声明的时候,使用的是复制构造函数,如:
class_name class1(1);
class_name class2(class1); //使用复制构造函数
class_name class3=class1; // 使用复制构造函数
已经构建的对象,会使用赋值,其实就是operator=的重载
class_name class4;
class4 = class1; //使用赋值
(8)动态分配内存(并非真正意义的动态,只是在遇到不同的情况能够进行不同的处理,与vector中的动态扩展内存不是一个概念):有的时候,并不知道对象应该给多大的空间,只是给了一些参数,这个时候就要用动态分配:
例如,有一个点SpreadsheetCell类如下:
class SpreadsheetCell
{
public:
void setValue(double inValue);
double getValue() const;
private:
double mValue;
};
现在需要创建一个类,里面可能包含多个点(每个点用两个数值坐标定位),但是具体多少个需要根据参数来决定,那么在进行构造的时候,就应当使用动态分配内存
class Spreadsheet
{
public:
Spreadsheet(int inWidth,int inHeight);
void setCellAt(int x,int y,const SpreadsheetCell& cell);
SpreadsheetCell& getCellAt(int x,int y);
private:
bool inRange(int val,int upper);
int mWidth,mHeight;
//因为数组尺度不同,为了动态分配,采用二维指针替代二维数组
SpreadsheetCell** mCells;
};
//采用动态分配内存的构造函数
Spreadsheet::Spreadsheet(int inWidth,int inHeight):
mWidth(inWidth),mHeight(inHeight)
{
mcells= new SpreadsheetCell* [mWidth];//先分配二维指针
for(int i=0;i<mWidth;i++)
{
mCells[i]=new SpreadsheetCell[mHeight];//每个一维指针指向一段连续内存的数组
}
}
相应的,应该有一个动态删除的析构函数如下:
//采用动态删除的析构函数
Spreadsheet::~Spreadsheet()
{
for(int i=0;i<mWidth;i++)
{
delete[] mCells[i];//后申请的先删除
}
delete [] mCells;
mCells=nullptr;
}
(9)复制的理解:(编译生成的复制构造函数和赋值语句只是表层复制,对于动态分配内存不适用)
初始化一个对象:Spreadsheet s1(4,3);
某个函数调用:func(s1);
这个时候传参是表层复制,只是复制了一个mCells的副本,它与原来的mCells指向同一块地址;对该副本的操作会直接改变对象的内存情况,危险!!
还有一种情况:
Spreadsheet s1(2,2),s2(4,3);
s1=s2;
这样执行之后,s1的指针mCells直接指向s2的内存区域,那么s1本来申请的区域被遗弃!内存泄漏!!
所以:如果在类里面动态分配了内存,就需要自己重新写复制构造函数和赋值运算符,以便于深层的内存复制。
重新定义复制构造函数:
Spreadsheet::Spreadsheet(const Spreadsheet& src)
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell*[mWidth];
//先创建底层的内存
for(int i=0;i<mWidth;i++)
{
mCells[i]= new SpreadsheetCell[mHeight];
}
//对底层的内存进行数值拷贝
for(int i=0;i<mWidth;i++)
{
for(int j=0;j<mHeigth;j++)
{
mCells[i][j]=src.mCells[i][j];
}
}
}
重载赋值运算符:(因为在赋值的时候已经初始化了,故而需要先释放已分配的内存,否则内存泄漏)
Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
//自检测,为了赋值的正确性
if(this == &rhs)
{
return *this;
}
//先删除已经分配的内存
for(int i=0;i<mWidth;i++)
delete[] mCells[i];
delete[] mCells;
mCells = nullptr;
//进行拷贝
mWidth = rhs.mWidth;
mHeight = rhs.mHeight;
mCells = new SpreadsheetCell*[mWidth];
for(int i=0;i<mWidth;i++)
{
mCells[i]= new SpreadsheetCell[mHeight];
}
for(int i=0;i<mWidth;i++)
{
for(int j=0;j<mHeigth;j++)
{
mCells[i][j]=rhs.mCells[i][j];
}
}
//该运算符有返回值
return *this;
}
(10) =default控制默认构造函数的生成,显式地指示编译器生成该函数的默认版本
=delete显式指示编译器不生成函数的默认版本
上一部分完整代码:
#include <iostream>
#include <vector>
#include <string>
#include<stdexcept>
using namespace std;
class SpreadsheetCell
{
public:
void setValue(double inValue);
double getValue() const;
private:
double mValue;
};
void SpreadsheetCell::setValue(double inValue)
{
mValue = inValue;
}
double SpreadsheetCell::getValue() const
{
return mValue;
}
class Spreadsheet
{
public:
Spreadsheet(int inWidth,int inHeight);
Spreadsheet(const Spreadsheet& src);
Spreadsheet& operator=(const Spreadsheet& rhs);
void setCellAt(int x,int y,const SpreadsheetCell& cell);
SpreadsheetCell& getCellAt(int x,int y);
~Spreadsheet();
private:
bool inRange(int val,int upper);
int mWidth,mHeight;
//因为数组尺度不同,为了动态分配,采用二维指针替代二维数组
SpreadsheetCell** mCells;
};
//采用动态分配内存的构造函数
Spreadsheet::Spreadsheet(int inWidth,int inHeight):
mWidth(inWidth),mHeight(inHeight)
{
mCells= new SpreadsheetCell* [mWidth];
for(int i=0;i<mWidth;i++)
{
mCells[i]=new SpreadsheetCell[mHeight];
}
}
//采用动态删除的析构函数
Spreadsheet::~Spreadsheet()
{
for(int i=0;i<mWidth;i++)
{
delete[] mCells[i];//后申请的先删除
}
delete [] mCells;
mCells=nullptr;
}
bool Spreadsheet::inRange(int val,int upper)
{
if(val>0&&val<upper)
return true;
else
return false;
}
void Spreadsheet::setCellAt(int x,int y,const SpreadsheetCell& cell)
{
if(!inRange(x,mWidth)||!inRange(y,mHeight))
throw std::out_of_range("");
mCells[x][y]=cell;
}
SpreadsheetCell& Spreadsheet::getCellAt(int x,int y)
{
if(!inRange(x,mWidth)||!inRange(y,mHeight))
throw std::out_of_range("");
return mCells[x][y];
}
Spreadsheet::Spreadsheet(const Spreadsheet& src)
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell*[mWidth];
//先创建底层的内存
for(int i=0;i<mWidth;i++)
{
mCells[i]= new SpreadsheetCell[mHeight];
}
//对底层的内存进行数值拷贝
for(int i=0;i<mWidth;i++)
{
for(int j=0;j<mHeight;j++)
{
mCells[i][j]=src.mCells[i][j];
}
}
}
Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
//自检测,为了赋值的正确性
if(this == &rhs)
{
return *this;
}
//先删除已经分配的内存
for(int i=0;i<mWidth;i++)
delete[] mCells[i];
delete[] mCells;
mCells = nullptr;
//进行拷贝
mWidth = rhs.mWidth;
mHeight = rhs.mHeight;
mCells = new SpreadsheetCell*[mWidth];
for(int i=0;i<mWidth;i++)
{
mCells[i]= new SpreadsheetCell[mHeight];
}
for(int i=0;i<mWidth;i++)
{
for(int j=0;j<mHeight;j++)
{
mCells[i][j]=rhs.mCells[i][j];
}
}
//该运算符有返回值
return *this;
}