类定义
类的基本思想是数据抽象和封装;数据抽象是一种依赖接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需要的的私有函数和成员。(简单理解:接口对外,实现对类内部)
1,构造函数:
每个类都分别定义了他的对象被初始化的方式,类通过一个或者几个特殊的成员函数来控制其对象的初始化过程。叫做构造函数。
目的是:初始化类对象的数据成员。
1),类内部构造:初始化列表
只有当类没有声明构造函数时,编译器才会自动的生成默认构造函数。
class Sales_data{ //四种构造函数
Salas_data()=default;//默认构造函数
Salas_data(const std::string&s):bookNo(s){}
Salas_data(const std::string&s,unsigned n,double p):
bookNo(s),units_sold(n),revenue(p*n){}
Salas_data(std::istream &);
//之前已有的其他成员
std::string isbn()const {return bookNo;}
Salas_data& combine(const Salas_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}
Salas_data(const std::string&s):bookNo(s){}
Salas_data(const std::string&s,unsigned n,double p):
bookNo(s),units_sold(n),revenue(p*n){}
冒号和花括号之间的代码称为构造函数初始值列表,其中花括号定义了(空的)函数体。
构造函数初始值列表负责为新创建的对象的一个或者几个数据成员赋值。它是成员名字的一个列表,每个名字后面用括号给初始值,不同成员的初始化通过都好分隔开。
Salas_data(const std::string&s):bookNo(s){}只为bookNo初始化,而units_sold和revenue则通过默认构造。
Salas_data(const std::string&s,unsigned n,double p):
bookNo(s),units_sold(n),revenue(p*n){}三个成员数据都显示的初始化了。
2),类外部定义构造函数
Salas_data::Salas_data(std::stream &is){
read(is ,*this);//*this是Salas_data对象的引用。
}
这个构造函数的初始化列表为空,但是由于执行了构造函数体,则其成员仍然能被初始化。当函数开始执行,则bookNo将被初始化为空string对象,而units_sold为0,revenue为0。
3)构造函数难点
1:若成员变量为引用或者const常量则必须初始化;建议使用构造函数初始化
class ConstRef{
public:
ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(ii){}
//不允许赋值
private:
int i;
const int ci;
int &ri;
};
2:成员初始化顺序和成员变量声明顺序有关。
建议:
尽量顺序和声明顺序一致;
尽量使用构造函数的参数作为成员的初始值;
尽量避免使用成员初始化其他成员
class X{
int x;int y;
public:
X(int val):y(val),(val){}//先初始化x在y
};
3:默认构造函数的使用发生条件
当我们在块作用域内不使用任何初始值定义一个非静态变量或者数组
当一个类本身含有类类型成员且使用合成的默认构造函数时
当类类型成员没有在构造函数初始值列表中显式的初始化时
4) 类的拷贝、赋值、析构:无动态分配内存即可拷贝赋值析构
当我们初始化变量以及以值的方式或返回一个对象时,会发生对象的拷贝。
当我们使用赋值运算时,会发生类对象的赋值操作。
当对象不在存在时,会执行销毁的操作。
(我们不需要主动定义这些操作,编译器会自动合成他们。俗称合成版本)
5),某些类不能依赖合成版本:当需要分配类对象之外的资源时。
动态分配内存与智能指针
我们的程序包括静态内存、栈内存和 堆区。
静态内存用来保存局部static 对象、类static数据成员、以及任何定义在函数之外的变量。
栈内存用来保存定义在函数内的非static对象。静态内存和栈内存可以被编译器自动创建和销毁。(静态对象在使用之前分配,栈内存在函数运行才存在)
堆区:自由空间区;程序用来储存动态分配,其生存期由程序控制,即程序员自己分配和销毁。
直接管理内存(new,delete):使用new动态分配内存并初始化
//分配内存
string *ps=new string;
int *ps=new int;//*ps指向一个空的字符串和未被初始化的int
//初始化:
int *ps =new int (5);//pi指向的对象的值是5;()内可以不赋值
string *ps =new string (10,"9");//*ps为"9999999999"
vector<int >*pv=new vector<int>{0,1,2}
//动态分配的const对象:const 对象必须进行初始化
const int *ps =new const int (1024);
释放动态内存
释放内存注意:
1,别忘记释放内存==
2,使用已经释放掉的内存==
3,同一块内存释放2次==
int i;*p1=&i;*p2=NULL;
double *p3=new double();*p4=p3;
delete p3;
delete p2;//释放空指针总是没有错误的
//其余delete都是错误的
返回指向动态内存的指针的函数给其调用者增加了一个额外的负担。调用者必须释放内存;
//例如有一个FOO的类型的动态分配对象
//factory函数返回一个指针,指向一个动态分配内存
Foo *factory(T arg){
//arg视情况处理
return new Foo(arg);//调用者负责释放此内存
}
//实例一
void use_factory(T arg){
Foo *P=factory(arg);
/*
使用p
*/
delete p;//记得释放内存
}
//实例二
Foo *use_factory(arg){
Foo *p=factory(arg);
return p;
}
2,访问控制与封装
public: 定义类的接口
private:可以被成员函数访问,但是不能被使用该类的代码访问。private 封装了类的实现细节。
1)友元
1,类可以允许其他类或者函数访问他的非公有成员,方法是令其他函数或者类成为他的友元。
2,类作为友元:如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员
3,类成员函数作为友元函数:必须指定该成员函数属于哪个类;函数必须在类之前被声明
1//普通友元函数
class Salas_data{
friend Salas_data add{const Salas_data&,const Salas_data&}
friend std::istream &read(std::istream&,Salas_data&);
friend std::isteam &print(std::ostream&,const Salas_data&);
}
//类作为友元:如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员
2//Screen类在下面
例如:
class window_mgr{
public:
using ScreenIndex=std::vector<Screen>::sizetype;//ScreenIndex表示vector<type>sizetype
void clear(ScreenIndex);
private:
std::vector<Screen>screens{Screen(24,80,'')};
}
void clear(ScreenIndex i){
Screen &s=screens[i];
s.cotents=string(s.height*s.widht,'');
}
3,成员函数作为友元
claa Screen{
public:
//clear函数必须在Screen类之前被声明
friend void window::clear(ScreenIndex);
//screen 类的其他部分
};
3,类的其他特性
//类Screen代表显示器的一个窗口。每个窗口包含一个用于保存Screen内容的
//contens和三个基础变量(光标的位置和屏幕的高宽)
class Screen {
public:
typedef std::string::size_type pos;
Screen &set(char); //screen 函数类型, pos 参数类型
Screen &set (pos,pos,char);
Screen &move(pos r,pos c);
const Screen &print(std ::ostream &os)const;
private:
pos cursor=0;
pos height,width;
std::string contents;
void display(std::ostream &os)const{os <<contents;}
//此const表示类的成员不可更改。
//只有类的非静态函数可以加const
//调用此函数的对象,在函数内是只读的.只能被这种const承诺的函数调用
};
//实现源文件
// Screen &可以作为左值(此类函数返回值是对象的引用,可以直接使用)
Screen &Screen::set(char c){
contents[curson]=c;
return *this;
}
Screen &Screen::set(pos r,pos col,char ch){
contents[r*width+col]=ch;
return *this;
}
Screen &Screen::move(pos r,pos c){
pos row=r*width;
curson=row+c;
return *this;
}
const Screen &Screen::print(std::ostream &os )const{
display(os);
return *this;
}
//例如:把光标移动到指定的位置,然后设置该位置的字符串
Screen myscreen;
myscreen.move(4,0).set("#");
4,类的作用域
详情见书(c++ primer第五版)253