基于《C++premier plus》第11、12章学习整理:
第十一章 使用类
一、运算符重载
1.建立
定义类成员函数或友元函数形如 返回类型 operator运算符(参数);可以理解为该函数名为形如operator+;
2.使用:
(1)类内成员:可以形如district2=(Person)sid+(?)sara;该重载运算符方法会被编译器自行替换到
district2=sid.operator+(sara);
(2)友元函数重载:可以形如district2=(int)sid+(Person)sara;该重载运算符方法会被编译器自行替换到
district2=operator+(sid,sara);
3.相关
(1)对类成员函数来说,调用对象会被当成前操作数。
(2)必须有一个操作数是用户定义的类型(防止用户为标准类型重载运算符)
(3)不能非凡运算符原来的句法规则。不能改变操作数数量,不能修改优先级,不能创建新运算符,不能重载部分运算符,=、[]、()、->只能通过成员方法重载。
警告:要非常小心运算符重载尤其是在定义了类型转换方法时的二义性。
二、友元函数
1.友元分为三种:友元函数、友元类、友元成员函数;是权限控制的一种扩展。
2.创建
(1)第一步将其原型放在类声明中且在前面加上关键字friend。
(2)第二步编写函数定义。因为不是成员函数所以不需要类名限定,也不需要在定义中标注friend。
3.特性
(1)不是成员函数也不能通过成员运算符调用
(2)具有和成员函数一致的访问权限
4.用友元函数方法来反转操作数顺序交给成员函数方法来执行会比较简单。
5.常用友元重载<<运算符进行输出。形如:
ostream & operator<<(ostream & os,const Time & t)
{
os<<t.hours<<" hours,"<<t.minutes<<" minutes.";
return os;
}
补充:源文件不需要包含头文件已经包含了的头文件。
6.成员重载运算符和友元重载运算符在本质上使用的操作数数目相同,成员的参数少一个是因为其中一个操作数是被隐式传递的调用对象。
三、类型转换(定义后默认采用自动转换)
1.构造函数缺少仅1值得类可以通过赋值单值得到对象。(实质上调用构造初始化后进行赋值)
2.关键字explicit可以关闭自动转换的特性,用于声明构造函数和类型转换函数都可以。
3.类型转换函数形如operator int() const;实现从对象到基本数据类型的转换。不能有参数、必须是类方法、不能指定返回类型。
4.应当慎重的使用隐式转换
四、其他
1.描述对象所在状态的成员被称为状态成员
2.如在类Vector中定义的枚举常量RECT,要在其他地方使用,应形如Vector::RECT;
3.头文件<cstdlib>包含了用于生成随机数的srand()和rand();
一般性:rand() % (b-a+1)+ a ; 就表示 a~b 之间的一个随机整数。
第十二章 类和动态内存分配
一、动态内存和类
1.静态变量在类中声明的不能初始化,需要在外部进行单独初始化,形如int StringBad::num_strings=0;不需要重申其static。(静态成员为枚举类型或const时可以声明同步初始化)
2.简单使用new构造与delete释放会出现问题的原因可能在类的特殊成员函数(都是如果没定义则自动提供的函数)
(1)有默认构造函数、默认析构函数、复制构造函数、赋值运算符、地址运算符(c++11额外提供了移动构造函数和移动赋值运算符<将在第十八章讨论>)
(2)复制构造函数隐式问题,本体形如Class_name(const Class_name &);
<1>新建一个对象(包括函数形参复制、返回值等)且以任何方式将之初始化为现有对象时将调用复制构造函数(包括形如StringBad * pStringBad=new StringBad(motto);)
<2>默认的复制构造函数逐个复制非静态成员(也称为浅复制),若成员有指针项则复制项的释放行为可能会引发严重错误。
<3>这里的解决方法是显式构造复制构造函数并且进行在其中进行对于内容的深拷贝
(3)赋值运算符隐式问题,本体形如StringBad & StringBad:: operator=(const StringBad&);
<1>将已有的对象赋给另一个对象时,将使用重载的赋值运算符
<2>其功能同复制构造函数一致,都是浅拷贝,可能造成数据受损。
<3>解决方法也是提供显式的赋值运算符定义进行深拷贝。
二、改进后的新string类
1.示例复制构造函数
StringBad :: StringBad(const StringBad & st)//实现深拷贝
{
num_string++;
len = st.len;
str=new char [len +1];
std::strcpy(str,st.str);
}
2.示例赋值运算符
StringBad & StringBad::operator=(const StringBad & st)
{
if(this==&st)
return *this;
delete [] str;
len =st.len;
str =new char[len+1];
std::strpcy(str,st.str);
return *this;
}
补充:c++11使用nullptr表示空指针,而之前用(void*)0和NULL表示
3.静态成员函数(此处转载
https://blog.csdn.net/fanyun_01/article/details/51422357)
(1)静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。举例如下:
class base{
static int func1();
int func2();
};
int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针
(2)静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。
(3)静态成员函数不可以同时声明为 virtual、const、volatile函数。举例如下:
class base{
virtual static void func1();//错误
static void func2() const;//错误
static void func3() volatile;//错误
};
(4
)静态函数不包含有编译器提供的隐藏的this指针,它在类没有实例化的时候就存在,所以可以直接用 (类名::函数) 来调用,并且由于没有this指针,所以也就没有特定的成员变量供它用,因为类没有实例化前,这些类成员变量不存在,系统也没有分配空间给这些变量,且没有this指针,也无法调用这些成员变量,所以他只能使用那些类没有实例化前就已经存在的静态变量。静态成员是可以独立访问的,无须创建任何对象实例就可以访问。
(5)普通成员函数即非静态函数因为new时传递了一个默认的this指针,所以意味着每一个对象都有一组自己的成员变量,这就意味着他可以使用这些成员变量,同时也可以使用静态成员变量,因为这些变量在对象还没new出来之前就已经存在。
(6)普通成员函数要通过对象调用,所以要求首先建立一个对象;而静态成员函数可不建立对象就可以被使用。因此,与类的非静态数据成员无关的成员函数,虽然可以被定义为非静态函数,但是如果定义为静态函数的话在使用上会更加方便。另外,如果类的成员函数想作为回调函数来使用,一般情况下只能将它定义为静态成员才行。
三、有关返回对象的说明
(0)可选情况下使用引用的方式进行对象的传递,可以提高效率。
四、使用指向对象的指针
(1)可以指向已有对象&,也可以创建新对象形如Class_name * pclass =new Class_name(value);为整个对象开辟空间。
(2)delete pclass;即可释放对象成员的内存,但如果对象成员为指针,其指向内存不会被释放(这个释放过程应该在析构函数中实现)。
(3)使用定位new运算符指定内存块存储对象时,只可对块进行delete而不可对该分配对象释放,由此其析构函数不会自动调用,这是需要显式调用析构函数的特殊情况之一。(注意此时应当与创建顺序反向的进行对象销毁)
五、队列模拟<以下为半成品,仅供参考>
file queue_simulator.h
ifndef QS_HEAD_
#def QS_HEAD_
#class Node{
public:
Node & getNext();
Node();
void start(int ah,int am);
bool isexit(int hour,int minute);
void setNext(Node &);
private:
static int occupyarray[3]={1,2,3};
static kind=3;
int occupy;//占用分钟数
int exit_hour;
int exit_minute;
Node * next;//下一个顾客
}
class Queue{
public:
void simulate(int start_hour,int start_minute,int continue);
Queue(int avnode);
private:
int average_node;
Node *now;
Node *tail;
static int array_lngth=0;
bool is_empty();
int count_array();
bool is_add();
void add(Node *temp);
void leave();
}
#endif
file queue_simulator.cpp
#include <iostream>
#include "queue_simulator.h"
#include <cstdlib>
Node::Node(){
int flag=rand() %(kind)+1;
occupy=occupyarray[flag];
exit_hour=0;
exit_minute=0;
}
Node & Node::getNext(){
return *next;
}
void Node::setNext(Node &newnode){
next=newnode;
}
void Node::start(int ah,int am){
exit_hour=ah+(am+occupy)/60;
exit_minute=(am+occupy)%60;
]
bool Node::isexit(int hour,int minute){
if(hour==exit_hour&&minute==exit_minute)
return true;
else
return false;
}
Queue::Queue(int avnode){
int average_node=avnode;
}
void Queue::simulate(int start_hour,int start_minute,int continue){
using namespace std;
int end_hour=start_hour+continue;
for(;start_hour<=end_hour;start_hour++){
for(;start_minute<=59;start_minute++){
cout<<"---------------"<<start_hour<<":"<<start_minute<<"-----------"<<endl;
if(is_empty())cout<<"now Queue is empty!"<<endl;
if(is_add()){
cout<<"a new customer come to the Queue!"<<endl;
Node *temp=new Node()
add();
}
if(now->is_exit(start_hour,start_minute)){
leave();
}
}
start_minute=0;
}
}
bool Queue::is_add(){
if(rand() % 61>average_node)
return true;
else
return false;
}
void Queue::add(Node *temp){
if(is_empty()){
now=temp;
tail=now;
now->start();
}else{
tail.setnext(temp);
tail=tail.getnext;
}
array_lngth++;
}
void Queue::leave(){
if (now==tail){
delete now;
continue;
}else{
Node *temp=now->next;
delete now;
now=temp;
now->start();
}
array_lngth--'
}
bool Queue::is_empty(){
if(now==nullptr)return ture;
else return false;
}
int Queue::count_array(){
return array_lngth;
}