- 书评
- 阅读方法
- 代码实战
- 复现
- Stash&Stack 1.0 ——简单的结构
- 访问控制——嵌套友元与迭代器初步
- Stash&Stack 2.0——添加访问控制
- 句柄类——封装的封装
- Stash&Stack 3.0 ——添加构造函数和析构函数
- SuperVar——Union的重载
- MyString 1.0——逐字节控制内存工具的应用以及开销的思考
- StringStack&Quoter——const 综合应用
- Comm——晦涩的volatile
- Stash&Stack 4.0——内联函数减少小函数调用开销
- 补充:内联理解
- 补充:宏的骚 *** 作
- 命名空间
- 引用与拷贝构造函数
- 引用
- 默认拷贝构造函数
- 拷贝构造函数
- 成员指针
- *** 作符重载
- 基本应用
- operator->/operator->* 与迭代器嵌入
- operator= 与赋值拷贝
- PStash——new和delete的动态内存管理
- new/delete概览
- PStash
- 自己写
这本书确实不适合新手上路,尤其是没学过c语言的,所以知乎上大多是喷的。
但是这类人大多没有看过编者按,实际上作者已经明确写出,假定学过c语言,有比较好的基础。
我感觉,要想学懂这本书,至少一本c primer plus是少不了的,因为这本书里面大量讲了编译,链接期间发生的行为,以及堆栈,调用等等系统相关知识,涉及到很多需要反复理解的名词,并且翻译还偶尔有不通顺,如果没点基础,直接劝退呗。
所以个人感觉,要学c++还是看c++ primer好,这本适合在c++的基础上,进一步学习面向对象以及系统知识,还有更高深的编程思想。
阅读方法我看过c primer plus,也会STL的基本用法,但是第一次看还是有很多地方看不懂,我记忆比较深的就是一些用到寄存器相关知识的,直接给我看破防了,但是还是硬着头皮啃下去了。
所以推荐二遍刷,第一遍用一周时间,全神贯注看,不会就查,有疑惑就敲代码实验,但是不必要看的特别懂,明白作者的意图就不错了。第一遍可以不敲代码,重要的是速通,打个基础,因为这书太高屋建瓴了,二刷才能更好地体会作者的思想。书里有源代码,给代码的有个好处,就是可以靠看来实现速刷,然后二刷来提高熟练度。
第一次刷完了,再代码二刷,全书的代码,最好把习题也连带上,都敲了,如果有别的实战项目也可以自己搞自己的,比如我就是要做这学期的面向对象上机作业,其中一个就是手写一个vector,我打算写个豪华版的,所以这个足够当做练习了。
第二遍要注意抓重点,有些东西就没必要了,或者选择性,比如make的分段编译之类的,如果以后不打算搞开发,学这个也没用,了解一下有这么个东西就行了。二刷的过程,是一次重新理解的过程,可以从更高的层次,以更接近作者思路的层次去理解书本,可以理解一些以前理解不了的东西,同时又是一次重新回忆的过程。
代码实战 复现这个模块主要是复现书上的,加一点自己的理解,注释。
复现不会选择所有例子,只会选择综合性例子。
Stash&Stack 1.0 ——简单的结构#include
#include
#include // 系统库,System调用相当于cmd
#include //c++级别字符串
#include //c++级别数组
#include //文件读写
#include //调试
using namespace std;
#ifndef STASH_H
#define STASH_H
const int increment = 100;
struct Stash
{
int size;//元素大小
int quantity;//元素个数上限
int next;//下一个的index
unsigned char* storage;//按Byte储存空间
void initialize(int size);
void cleanup(void);
int add(const void* elemtent);
void* fetch(int index);
int count();
void inflate(int increase);
};
void Stash::initialize(int sz)//通过size定型
{
size = sz;
quantity = 0;
storage = 0;
next = 0;
}
int Stash::add(const void* element)
{
if (next >= quantity)//兼顾从0到1以及从少到多
inflate(increment);
int startByte = next * size;
unsigned char* e = (unsigned char*)element;//转换类型以逐Byte赋值
for (int i = 0; i < size; i++)
{
storage[startByte + i] = e[i];
}
next++;
return (next - 1);
}
void* Stash::fetch(int index)
{
if (index < 0 || index >= next)
{
return NULL;
}
return &(storage[index * size]);
}
int Stash::count()
{
return next;
}
void Stash::inflate(int increase)
{
if (increase <= 0)//确保increase大于0
{
return;
}
int newQuantity = quantity + increase;
int newBytes = newQuantity * size;
int oldBytes = quantity * size;
unsigned char* new_s = new unsigned char[newBytes];//申请新空间
if (!new_s)//确保成功
return;
for (int i = 0; i < oldBytes; i++)//逐Byte复制
{
new_s[i] = storage[i];
}
delete[] storage;//删除原来的
storage = new_s;
quantity = newQuantity;
}
void Stash::cleanup()
{
if (storage != NULL)
{
cout << "clean up" << endl;
delete[] storage;
}
}
#endif //STASH_H//
int main(void)
{
//整数测试,装填int
Stash intStash;
intStash.initialize(sizeof(int));
for (int i = 0; i < 20; i++)
{
intStash.add(&i);
}
for (int j = 0; j < intStash.count(); j++)
{
cout << "intStash.fetch("
<< j << ") = "
<< *(int*)intStash.fetch(j) << endl;
}
//string测试,装填string对象
Stash stringStash;
const int bufsize = 80;//默认string最长长度
stringStash.initialize(sizeof(char) * bufsize);
ifstream in("input.txt");
string line;
while (getline(in, line))
{
//string::c_str,将string对象转化为c-char array
stringStash.add(line.c_str());//这里装填固定的长度,超出去的是垃圾,提前被}截断
int
= k 0 ;char
*; cpwhile
( (=cp ( char*).stringStashfetch(++k))!= NULL )<<
{
cout "stringStash.fetch(" << << k ") = " << << cp ; endl}
.
intStashcleanup();//防止内存泄露 .
stringStashcleanup();return
0 ;}
#
include#
include#
include// 系统库,System调用相当于cmd #
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace ; std#
ifndefSTACK_H //防止重复声明结构,函数没有这个问题#
defineSTACK_H struct
Stack //嵌套结构,顺便定义个成员 {
//注意这个链表不同于常规链表,里面的data是指针,这是为了不同类型
struct
Link void {
*; data*
Link; nextvoid
initialize (void*, dat* Link) nxt;}
*;headvoid
initialize ();void
push (void*) data;void
*peek ();void
*pop ();void
cleanup ();}
;void
:: StackLink::initialize(void*, dat* Link) nxt//节点初始化=
{
data ; dat=
next ; nxt}
void
Stack ::initialize()//初始化头部为NULL=
{
head NULL ;}
void
Stack ::push(void*) dat//将新节点插到头部,注意是直接把元素插进去了,没有复制 *
{
Link= node new ; Linkinitialize
node->(,dat) head;=
head ; node}
void
*Stack ::peek()//获取顶部元素 if
{
( ==head NULL )//判断空 return
NULL ;return
; head->data}
void
*Stack ::pop()if
{
( ==head NULL )//判断空return
NULL ;void
*= result ; head->data//暂时储存 *
Link= old_head ; head=
head ; head->next//移位 delete
; old_head//处理暂存 return
; result}
void
Stack ::cleanup()<<
{
cout "clean up" << ; endl}
#
endif//STACK_H// int
main (void)in
{
ifstream ("input.txt");;
Stack lineStack.
lineStackinitialize();;
string linewhile
( getline(,in) line).
{
lineStackpush(newstring ()line);//拷贝构造一份push进去//lineStack.push(&line);
/*
//这样会有问题,可以自己画个指针图来求解
//cout << &line << endl; //line的地址不变
//三个data都指向了line变量的内存位置,而line最后变为NULL
//后面的delete NULL就会报错
*/
}
*
string; lpwhile
( (=lp ( *string).lineStackpop())!= NULL )<<
{
cout * <<lp ; endldelete
; lp//lp得到的是data的指针,用完元素记得清理}
.
lineStackcleanup();return
0 ;}
#
访问控制——嵌套友元与迭代器初步
include#
include#
include// 系统库,System调用相当于cmd #
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace ; stdconst
int = sz 20 ;//嵌套友元与迭代器初步
struct
Holder private {
:int
[ a]sz;public
:void
initialize ()memset
{
(,a0 ,* sz sizeof (int));}
struct
Pointer ;//不完全声明 friend
; Pointer//仅嵌套结构,不进行组合,实际上是一个迭代器,之所以嵌套是为了表达所属关系
struct
Pointer private {
:*
Holder; hint
*; ppublic
://函数全部内联,因为太短了
void
initialize (*Holder) rv=
{
h ; rv//绑定Holder=
p ; h->a//获取Holder容器的访问权限 }
//以下为指针移动与存取 *** 作,通过Pointer来间接实现
void
next ()if
{
( <p & ([h->a-sz 1 ]))//通过地址判断越界++
{
p;}
}
void
previous ()if
{
( &p > ([h->a0]))--
{
p;}
}
void
top ()=
{
p & ([h->a0]);}
void
end ()=
{
p & ([h->a-sz 1 ]);}
int
read ()return
{
* ;p}
void
set (int) i*
{
=p ; i}
}
;}
;int
main (void);
{
Holder h::
Holder,Pointer hp; hp2int
; i.
hinitialize();.
hpinitialize(&)h;.
hp2initialize(&)h;for
( =i 0 ;< i ; sz++ i).
{
hpset()i;.
hpnext();}
.
hptop();//双指针头尾反向遍历.
hp2end();for
( =i 0 ;< i ; sz++ i)<<
{
cout "hp = " << . hpread()<<
", hp2 = " << . hp2read()<< ; endl.
hpnext();.
hp2previous();}
return
0 ;}
//就是简单的加一下access specifier即可,实现不需要改变,成员函数随便访问private
Stash&Stack 2.0——添加访问控制
#
include#
include#
include// 系统库,System调用相当于cmd #
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace ; std#
ifndefSTASH_H #
defineSTASH_H const
int = increment 100 ;struct
Stash public
{
:void
initialize (int) size;//定型 void
cleanup (void);//清空int
add (constvoid *) elemtent;//添加 void
*fetch (int) index;//读取 int
count ();//查询数量private
:int
; size//元素大小int
; quantity//元素个数上限int
; next//下一个的indexunsigned
char *; storage//按Byte储存空间void
inflate (int) increase;//扩容方法}
;#
include#
include#
include// 系统库,System调用相当于cmd #
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace ; std#
ifndefSTACK_H //防止重复声明结构,函数没有这个问题#
defineSTACK_H struct
Stack //嵌套结构,顺便定义个成员 {
//注意这个链表不同于常规链表,里面的data是指针,这是为了不同类型
public
:void
initialize ();void
push (void*) data;void
*peek ();void
*pop ();void
cleanup ();private
:struct
Link void {
*; data*
Link; nextvoid
initialize (void*, dat* Link) nxt;}
*;head}
;/*
句柄类用法,用一个只有public方法的类将一个具有private方法的类包装起来
使用不完全定义的嵌套类,并将其指针包含进去,只暴露指针
好处1:隐藏
好处2:修改结构成员和private都不会影响头文件,不使用句柄类就得同时重新编译头文件和目标程序,现在只需要编译头文件即可,目标程序因为头文件不变不需要重新编译.
*/
句柄类——封装的封装
#
include#
include#
include// 系统库,System调用相当于cmd #
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace ; std#
ifndefHANDLE_H #
defineHANDLE_H class
Handle public {
:void
initialize ();void
cleanup ();int
read ();void
change (int);private
:struct
Cheshire ;//不完全定义 *
Cheshire; smile//用户只能看到这个,至于指向的地方是什么样的(源代码)无法把握 }
;//以下为自己放在别处的实现
struct
Handle ::Cheshire//这里可以加各种private,这里仅仅简单做个类 { int
; i}
;void
Handle ::initialize()=
{
smile new ; Cheshire=
smile->i 0 ;}
void
Handle ::cleanup()delete
{
; smile}
int
Handle ::read()return
{
; smile->i}
void
Handle ::change(int) x=
{
smile->i ; x}
#
endif//HANDLE_H// int
main (void);
{
Handle u.
uinitialize();<<
cout . uread()<< ; endl.
uchange(1);<<
cout . uread()<< ; endl.
ucleanup();return
0 ;}
#
Stash&Stack 3.0 ——添加构造函数和析构函数
include#
include#
include// 系统库,System调用相当于cmd #
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace ; std#
ifndefSTASH_H #
defineSTASH_H const
int = increment 20 ;class
Stash public {
:Stash
(int) size;~
Stash();int
add (constvoid *) element;void
*fetch (int) index;int
count ();private
:int
; sizeint
; quantityint
; nextunsigned
char *; storagevoid
inflate (int) increase;}
;Stash
::Stash(int) sz: size()sz,quantity (0),storage (NULL),next (0)<<
{
cout "stash constructor" << ; endl}
Stash
::~Stash()<< {
cout "stash destructor" << ; endl}
int
Stash ::add(constvoid *) elementif
{
( )next >= quantity//兼顾从0到1以及从少到多inflate
()increment;int
= startByte * next ; sizeunsigned
char *= e ( unsignedchar *);element//转换类型以逐Byte赋值for
( int= i 0 ;< i ; size++ i)[
{
storage+startByte ] i= [ e]i;}
++
next;return
( -next 1 );}
void
*Stash ::fetch(int) indexif
{
( <index 0 || ) index >= nextreturn
{
NULL ;}
return
& ([storage*index ] size);}
int
Stash ::count()return
{
; next}
void
Stash ::inflate(int) increaseif
{
( <=increase 0 )//确保increase大于0return
{
;}
int
= newQuantity + quantity ; increaseint
= newBytes * newQuantity ; sizeint
= oldBytes * quantity ; sizeunsigned
char *= new_s new unsigned char []newBytes;//申请新空间if
( !)new_s//确保成功return
;for
( int= i 0 ;< i ; oldBytes++ i)//逐Byte复制[
{
new_s]i= [ storage]i;}
delete
[]; storage//删除原来的=
storage ; new_s=
quantity ; newQuantity}
#
endif//STASH_H// int
main (void)//整数测试,装填int
{
intStash
Stash (sizeof(int));for
( int= i 0 ;< i 20 ;++ i).
{
intStashadd(&)i;}
for
( int= j 0 ;< j . intStashcount();++ j)<<
{
cout "intStash.fetch(" <<
<< j ") = " <<
* (int*).intStashfetch()j<< ; endl}
//string测试,装填string对象
const
int = bufsize 80 ;//默认string最长长度stringStash
Stash (*bufsizesizeof(char));in
ifstream ("input.txt");;
string linewhile
( getline(,in) line)//string::c_str,将string对象转化为c-char array
{
.
stringStashadd(.linec_str());//这里装填固定的长度,超出去的是垃圾,提前被}截断int
=
0 k ; char*
;while cp(
( =(cp char *).fetchstringStash(++)k)!=NULL ) <<"stringStash.fetch("
{
cout << << ") = " k << << ; cp } endl//析构执行点
return
0
; }#
include
#include
#include
// 系统库,System调用相当于cmd# include
//c++级别字符串# include
//c++级别数组# include
//文件读写# include
//调试using namespace
; # stdifndef
STACK_H# define
STACK_Hclass Stack
public : {
Stack(
);~Stack
();voidpush
( void*); datvoid*
peek( );void*
pop( );private:
structLink
void * {
;* data;
LinkLink next(
void*,* dat) Link; nxt//所有类都应该有四大函数~ Link
();}*
;}head;
::Link
Stack::Link(void*,* dat) Link: nxtdata (),datnext( )<<nxt"Stack::Link "
{
cout << <<" constructor"data<<; } endl::
Link
Stack::~Link()<<"Stack::Link "
{
cout << <<" destructor"data<<; } endlStack
::
Stack():head (NULL)<<"Stack constructor"
{
cout << ; } endlStack
::
~Stack()<<"Stack destructor "
{
cout << ( == NULLhead ? "success":"fail")<<;}endlvoid
Stack
:: push(void*)//将新节点插到头部,注意是直接把元素插进去了,没有复制 dat* =
{
Linknew node Link ( ,)dat; head=;
head } nodevoid
*
Stack:: peek()//获取顶部元素if (
{
== NULLhead ) //判断空return NULL
; return;
} head->datavoid
*
Stack:: pop()if(
{
== NULLhead ) //判断空returnNULL
; void*
=; result //暂时储存 head->data* =
Link; old_head = head;
head //移位 head->nextdelete ;
//处理暂存 old_headreturn ;
} result#
endif
//STACK_H//int main
( void)in(
{
ifstream "input.txt");;//构造函数没有参数就不用带括号了
Stack lineStack;while
string line(
getline (,)in) line.push
{
lineStack(newstring( ))line;//拷贝构造一份push进去//lineStack.push(&line); /*
//这样会有问题,可以自己画个指针图来求解
//cout << &line << endl; //line的地址不变
//三个data都指向了line变量的内存位置,而line最后变为NULL
//后面的delete NULL就会报错
*/
}
*
;
stringwhile lp(
( =(lp * )string.poplineStack())!=NULL ) <<*
{
cout << ;lp delete endl;
//lp得到的是data的指针,用完元素记得清理,之所以不将清理内存放在 lp//析构函数中,是因为传出来的不是拷贝,而是指针,这个程序的根本性问题就在于//全都是用的指针,即使是在一些应该有拷贝的地方,这就极易出问题
}
return
0
; }#
include
SuperVar——Union的重载
其实没啥用,因为已经失去了Union最本真的用途了,只是演示Union也可以像类一样有四大函数,也可以重载构造函数.
#include
#include
// 系统库,System调用相当于cmd# include
//c++级别字符串# include
//c++级别数组# include
//文件读写# include
//调试using namespace
; class stdSuperVar
public : {
SuperVar(
char): chc(),chvartype()<<character"SuperVar constructer char"
{
cout << ; } endlSuperVar
(
int): iii (),iivartype()<<integer"SuperVar constructer int"
{
cout << ; } endlSuperVar
(
float): fff (),ffvartype()<<floating_point"SuperVar constructer float"
{
cout << ; } endlvoid
print
( )switch(
{
) casevartype:
{
<< character"char: "
cout << << ; c break endl;
case:
<< integer"int: "
cout << << ; i break endl;
case:
<< floating_point"float: "
cout << << ; f break endl;
default:
<<"error"
cout << ; } endl}
private
:
enum//匿名枚举
, { ,
character}
integer;
floating_point
unionvartype//匿名联合,可以直接 *** 控变量
char {;
int c;
float i;
} f;
};
intmain
( void)A(
{
SuperVar 'c'),B( 1),C( 1.2f);//这里必须通过f后缀声明floatD (
SuperVar float(1.2));//或者显式转化. print
A();.print
B();.print
C();.print
D();return0
; }#
include
MyString 1.0——逐字节控制内存工具的应用以及开销的思考
其实之前的stash和stack都可以用这个做,char的另一个优势在于可以用strcpy,strcat等各种函数,以及memset,memcpy等等,估计这些也是用底层内存写的。
#include
#include
// 系统库,System调用相当于cmd# include
//c++级别字符串# include
//c++级别数组# include
//文件读写# include
//调试using namespace
; # stdifndef
MEM_H # define
MEM_H//一个辅助内存块管理工具类 typedef unsigned
char ; //基本内存单位 byteclass Mem
public : {
//不使用重载而是默认参数,因为这两个没有开销区别/*Mem() :mem(0), size(0)
{
cout << "Mem constructor" << endl;
}*/
Mem
(
int=0 sz):mem (0),size( 0)//分配sz的空间<< "Mem constructor "
{
cout << << " Bytes" sz << ; ensureMinSize endl(
);sz}~
Mem
()<<"Mem destructor"
{
cout << ; delete endl[
];//可以delete NULL mem} int
getSize
( )//总空间return ;
{
} size*
pointer
byte( )//返回空间指针,有需要可以转化为各种格式的指针对应类型return ;
{
} mem*
pointer
byte( int)//返回前确保空间 minSizeensureMinSize (
{
);minSizereturn;
} memprivate
:
*;
byteint mem;
//剩余空间 sizevoid ensureMinSize
( int); minSize};
voidMem
:: ensureMinSize(int)if minSize(
{
< )size //空间不足 minSize//新空间申请与清理,只清理后面的空间是因为前面的会被覆盖,没必要 *
{ =
bytenew newmem [ ] byte;minSizememset(
+,newmem 0 size, -) minSize ; size//转移处理老空间memcpy
(
,,newmem) mem; sizedelete[
];//更新 mem=
;
mem = newmem;
size } minSize}
#
endif
//MEM_H//# ifndef
MYSTRING_H# define
MYSTRING_Hclass MyString
public : {
MyString(
):buf (NULL)<<"MyString constructor"
{
cout << ; } endlMyString
(
constchar* )<< str"MyString constructor"
{
cout << ; = endlnew
buf Mem ( strlen()+str1 ) ;//多一个strcpy的空间( (
char*)pointer()buf->,);//将空间视作char* str}~ MyString
(
)<<"MyString destructor"<<
{
cout ; delete ; endl//调用Mem析构函数,析构调用是递归的
} bufvoidconcat
(
const char*) //定义空串行为if str(
{
!
) =newbuf;
{
buf } //确保空间具有 原来串+新串长度+1的空间 Memstrcat
(
(
char*)pointer(getSizebuf->()buf->+strlen( ) +1str) , );} strvoidprint
(
& =)ostream//将串输出到目标位置,默认为cout osifcout( !
{
) return;buf}
{
<<pointer
(
os ) buf-><<;} private endl:
*
;//指向一个Mem类,Mem类里面有指针指向空间
Mem} buf; #
endif//MYSTRING_H//
intmain (
void )s("My test string"
{
MyString );.print(
s);.concat(
s" some additional stuff");.print(
s);;.concat
MyString s2(
s2"Using defalult constructor");.print(
s2);return0;
} #include
#
StringStack&Quoter——const 综合应用
include#
include// 系统库,System调用相当于cmd
#include //c++级别字符串
#include //c++级别数组
#include //文件读写
#include //调试
usingnamespace ;
# ifndef stdSTRINGSTACK_H
#define STRINGSTACK_H
classStringStack public
: StringStack {
()
:index( 0)memset(,
{
0,stack* sizeof( size * ))string;}voidpush
(
const *)//确保不会修改,所以用const指针,但是仅仅是我们这个指针const string//其他地方指向同样string的可能修改这个string sif
(
<
{
) [index ++ size]
{
stack=index;} } sconst
*
pop
( string) //不允许返回值修改,所以返回const对象if( 0
{
) constindex > *=
{
[ string-- rv ] stack;//现在rv和stack[index]同时指向stringindex[]=
stackNULLindex; //释放一个指针 return;}
return rvNULL
;
} private:
static
constint
= 100 ; size //static将类中const放到编译期间,全局const也在编译期间 const*[
] string; stack//size个const指针sizeint; //指向下一位
} index;#
endif//STRINGSTACK_H
intmain (
void )[]=
{
string iceCream"1,", "2,"
{
,"3,","4,"};//自动计算长度
constint
=
sizeof ( iCsz ) /sizeoficeCream( * );;iceCreamfor(
StringStack ssint
= 0; i < ;++ i ) iCsz. ipush(
{
ss&[])iceCream;i//非const赋const}const*
;
while string( cp(
= .popcp ( ss))!=NULL) << *<<
{
cout ; }cp //以下证明const指针仅仅保护通过当前指针的路径不被修改,别的路还可以修改 endl[
2
]
iceCream="changed by no-const pointer"; for (int
= 0; i < ;++ i ) iCsz. ipush(
{
ss&[])iceCream;i//非const赋const,从iceCream可以修改,但是不可以从ss修改}while(
(
= .popcp ( ss))!=NULL) << *<<
{
cout ; }cp return endl0
;
} #include
#
include#
include// 系统库,System调用相当于cmd
#include //c++级别字符串
#include //c++级别数组
#include //文件读写
#include //调试
#include //时间相关
usingnamespace ;
class Quoter stdpublic
: Quoter {
()
;intlastQuote(
) const;//const成员函数,当对象被声明为const就不能调用非const方法 constchar *
quote () ;private:int
;}
; lastquoteQuoter
::Quoter
():lastquote(-1)srand(time
{
(0));}intQuoter
::
lastQuote ()const//声明和定义都要加const,统一仍然适用return ; }
{
const lastquotechar
*
Quoter ::quote ()//返回一个const*,但是会改变成员(lastquote)staticconstchar
{
* [ ]= quotes//static const,编译期间量"quote-1" , { "quote-2"
,"quote-3"
,"quote-4"
,"quote-5"
};
const
int=
sizeof / qsize sizeof * quotes ; int = quotesrand
( qnum ) %;//随机获取一个范围内下标 while qsize( 0
&& ==lastquote >= ) //不取和上一次相同的或者-1 qnum = lastquoterand(
{
qnum ) %;} return qsize[
=
] quotes;lastquote } qnumintmain
(
) ;const;
{
Quoter q.
lastQuote Quoter cq(
cq);// cq.quote() 压根不会出现在备选列表里for(
int
= 0; i < 20; i ++ )//q就可以调用,因为q不是const对象 i<<. quote
{
cout ( q)<<;} return endl0
;
} #include
#
Comm——晦涩的volatile
这个初学不会用,volatile的意思是不要让编译器进行想当然优化,也就是我们做好了出现意外情况的思想准备。比如我已经做好了被多线程修改的准备,就不要让编译器拒绝多线程。
include#
include// 系统库,System调用相当于cmd
#include //c++级别字符串
#include //c++级别数组
#include //文件读写
#include //调试
#include //时间相关
usingnamespace ;
class Comm stdpublic
: Comm {
()
;voidisr(
) volatile;char read(
int )const; indexprivate :enum
=100
}{bufsize;//等效 *** 作//static const int bufsize = 100;constvolatile
unsigned
char ; volatile unsigned bytechar
; unsigned char flag[
] ; bufintbufsize;}
; indexComm
::Comm
():index( 0),byte(0 ),flag(0 )<<"Comm constructor"<<
{
cout ; } void endlComm
::
isr ()volatile=0 ;
{
flag [ ++]
buf=index;if ( byte)
= 0index >= bufsize;
{
index } }char
Comm
::
read (int)constif index( <
{
0 ||index ) return 0 index >= bufsize;
{
} return[
]
; buf}indexintmain
(
void )volatile;.
{
isr Comm Port(
Port);//Port.read(0); //不行,不是volatilereturn0
;
} #include
#
Stash&Stack 4.0——内联函数减少小函数调用开销
include#
include// 系统库,System调用相当于cmd
#include //c++级别字符串
#include //c++级别数组
#include //文件读写
#include //调试
usingnamespace ;
# ifndef stdSTASH_H
#define STASH_H
constint =
20 ; increment class Stashpublic
: Stash {
(int
):size sz( ),quantitysz(0 ),storage(NULL ),next(0 )<<"stash constructor"<<
{
cout ; } ~ endlStash
(
)<<"stash destructor"<< {
cout ; } int endladd
(
const void*) ;//非内联 elementvoid* fetch
(int )if( index<
{
0 ||index ) return NULL index >= next;
{
} return&
(
[ *]storage)index ; size}intcount
(
) return;}
{
private next:
int
;int
; sizeint
; quantityunsigned
char next*
; voidinflate storage(
int );//非内联 increase};int
Stash::
add (constvoid*) if( element)
{
//兼顾从0到1以及从少到多 inflatenext >= quantity()
;intincrement=*
; startByte unsigned next char size*
= (unsigned e char *) ;//转换类型以逐Byte赋值forelement(int
= 0; i < ;++ i ) size[ i+]
{
storage=startByte [ i] ; e}i++;
return
next(-
1 )next ; }voidStash
::
inflate (int)if( increase<=
{
0 )increase //确保increase大于0 return;}
{
int=
+
; newQuantity int quantity = increase*
; newBytes int newQuantity = size*
; oldBytes unsigned quantity char size*
= newunsigned new_s char [ ] ;//申请新空间newBytesif(!
) //确保成功returnnew_s;for
(int
= 0; i < ;++ i ) oldBytes//逐Byte复制 i[]=
{
new_s[i] ; storage}idelete[
]
;//删除原来的= storage;=
storage ; new_s}
quantity # newQuantityendif
//STASH_H//
intmain (
void )//整数测试,装填int intStash(
{
sizeof
Stash (int));for(int
= 0; i < 20; i ++ ). iadd(
{
intStash&);}ifor(
int
= 0; j < .count j ( intStash);++)<< j"intStash.fetch("<<
{
cout << ") = "
<< j * (
int *).fetch(intStash)<<;j} //string测试,装填string对象 endlconst
int
=
80 ; bufsize //默认string最长长度 stringStash(*
Stash sizeof(bufsize char ));in("input.txt"
ifstream );;while(
string linegetline
( ,))in//string::c_str,将string对象转化为c-char array line.add
{
(
stringStash.c_str(line));//这里装填固定的长度,超出去的是垃圾,提前被}截断int=0
;
char k * ;while
(( cp=
( char*cp ) .fetch(++stringStash))!=kNULL)<< "stringStash.fetch(" <<<<
{
cout ") = " << << k ; } //析构执行点 cp return endl0
;
}
# include#
include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; #
ifndef STACK_H std#
defineSTACK_H class
Stackpublic :
Stack ( {
):
head(NULL )<<"Stack constructor"<<;
{
cout } ~ Stack endl(
)
<<"Stack destructor "<<(
{
cout == NULL ? "success"head : "fail" ) << ; }void push endl(
void
* )//将新节点插到头部,注意是直接把元素插进去了,没有复制*= datnew Link
{
Link( node , ) ;=dat; head}void
head * nodepeek
(
)//获取顶部元素 if(== NULL
{
) //判断空head return NULL; return
; }void
* head->datapop
(
)//这个也不算大,就内联了 if(== NULL
{
) //判断空head return NULL;void
* =;
//暂时储存* result = head->data; =
Link; old_head //移位 headdelete
head ; head->next//处理暂存 return
; old_head} private
: resultstruct
Link
void*
; * {
;Link data(
Linkvoid next*
,*): datdata Link( nxt) ,next(dat)//所有类都应该有四大函数 <<"Stack::Link "nxt<<<<
{
cout " constructor" << ; data } ~ Link endl(
)
<<"Stack::Link "<<<<
{
cout " destructor" << ; data } } * endl;
}
;#headendif
//STACK_H//int
main( void
) in("input.txt")
{
ifstream ;;//构造函数没有参数就不用带括号了;while
Stack lineStack(getline
string line(
, )).inpush line(new
{
lineStackstring()) ;//拷贝构造一份push进去line//lineStack.push(&line); /*
//这样会有问题,可以自己画个指针图来求解
//cout << &line << endl; //line的地址不变
//三个data都指向了line变量的内存位置,而line最后变为NULL
//后面的delete NULL就会报错
*/}*
;
while
(
string( lp=
( *)lp . popstring()lineStack)!=NULL)<< * <<;
{
cout delete ;lp //lp得到的是data的指针,用完元素记得清理,之所以不将清理内存放在 endl//析构函数中,是因为传出来的不是拷贝,而是指针,这个程序的根本性问题就在于
//全都是用的指针,即使是在一些应该有拷贝的地方,这就极易出问题 lp}return
0
;
}
class Xpublic
:
补充:内联理解
普通函数有自己的内存,地址,需要进行调用,而调用这一步具有比较大的开销,如果能直接将函数调用替换为源代码就好了,这实际上就是类似于宏的用法。
对于宏,宏其实是保存在符号表里的,占用空间,内联函数不仅有宏一样的特性,储存在符号表,然后替换,可以有效减少调用开销,而且还具有编译器的类型检查功能,这是宏没有的。代价就是占用符号表空间,如果内联函数太大,空间占用带来的替换开销或许就会超过调用的开销。
所以有一个原则,小函数内联,大函数和带循环的(展开后代码膨胀)调用。形式上没什么区别,本质上区别在于宏与非宏。如果要在性能上下功夫,内联是可以考虑的一个点,实际上比较复杂。
需要注意的是,inline只是一个建议,类似于register,编译器会自己进行判断是否采纳。
void common_func {
()
; //普通调用函数voidinline_out_class( )
; voidfunc_inline_in_class()
//内部直接写默认内联 <<"inline"<<;
{
cout } } ; endlvoid
X
::common_func
( )<<"common function"<<;
{
cout } inline void endlX
::
inline_out_class ( )//外部内联,需要inline<<"inline"<<;
{
cout } 字符串定义 字符串转换——用于字符串拼接 endl标志符粘贴
#
补充:宏的骚 *** 作
- include#include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; //定义函数语句最后的;不用加,因为用的时候会加
# define stdSTRING
"nihao"
//字符串定义# define DEBUG (
)<< " = "<<x<< cout //字符串拼接,#将标志符直接变成字符串 #x # define x TRACE endl (
)<< <<;s# cerrdefine#sLABELendl( s
)## =//标志粘贴,##将i直接替换到标志符,提供代码级的重用iint string label_maini(#i void
) <<"字符串定义: "<<<<
{
cout ; <<"字符串拼接:"STRING ; endl=
cout "nihaoya" ;DEBUG
string str();
<<"TRACE: "str;TRACE
cout ( );
LABEL(str1)
;LABEL(2)
;<<"label_1: "<<<<
cout " label_2: "<< << label_1 ; return 0 label_2 ; endl}
# include#
include
命名空间
这个别想了,初学没用处的,这是做大项目才会用到的,最多学一下using指令。
引用与拷贝构造函数 引用引用的基本概念比较简单,就是用法需要注意一点。
const是需要注意的,如果不需要改,或者营造出一种传值的感觉,那就用const引用,否则就是要改了,要改的话用引用有可能引起混淆,因为外面的人不知道你是按值传递还是传引用。
所以按值传递就无脑const引用即可
要改的话就得想想用引用还是指针
默认拷贝构造函数常理来说,拷贝构造应该是直接复制一份,如下实验说明了这个假设:
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; //测试默认拷贝构造函数
class X stdpublic
:
* ; {
};
stringint pmain
(void
) ;="nihao";
{
X x.
string str = &;
x=p ; //默认cc函数str<<
X y "xp: " x<< .
cout << ; << x"yp: "p << endl.
cout << ; return y0p ; endl}
目的。定义按值传递时的行为 写法。同构造函数,只不过参数只有一个,class &,有时候会是const class &,其实个人感觉const更好,因为拷贝构造一般不会修改原有对象的。 为什么是传引用呢?一方面比较有效率,另一方面,拷贝构造函数就是为了定义类按值传递时的行为,不用引用的话,你还没定义呢,你就按值传递了。
初始化列表。不是构造函数就是拷贝构造函数,说白了,构造和拷贝构造是同级的,只不过场景不同。
可以看到,两个指针的内容一样,这就是默认的CC函数,符合假定,也就是按位拷贝。
但是这样也会带来一个问题,就是不适合处理复杂的类,比如带指针的。你光是复制了指针,不复制人家指向的对象,就不行,所以得自己编写拷贝构造函数来实现复杂的 *** 作。
拷贝构造函数- 特殊应用:私有拷贝构造。这个可以阻止按值传递。 #include#include
本质:定义的时候表示一种相对于基地址的偏移,针对一个类,而不针对某个对象
所以没有精确指向某个对象的成员的指针,只有指向一个类中成员的指针,然后配合对象或者对象指针来使用。
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; class
Data public std:
int , {
, ;
void aprint b( c)
const <<"a = "<< <<
{
cout ", b = " << << a ", c = " << << b ; } } c ; endlint
main
(void
) ;*=&
{
Data d;
Data//重点解析这个定义式 dp /*
不过是在 *指针名 前面加个作用域,表示针对这个类
前面的类型表明只针对这种类型的成员
右边规定了指针指向类中哪个成员,写法仅仅符合指针写法,不具有取地址实际意义
*/ intd::
*
=
& Data::; memPointerInt . *Data=a1
d;//点号用法memPointerInt = &:: ;
memPointerInt //更换指向成员 *Data=b2;
dp->=memPointerInt & ::;
memPointerInt . *Data=c3
d;//函数成员指针类似,也是加个作用域,右边仅针对一类memPointerInt void (::
*
) (Data) const funcPointer=&:: ; ( .Data*print)
(d);funcPointer//这里要注意前面整体相当于函数名,要括起来return0; }
成员函数不需要传自己,并且可以使用this进行对自己的引用和 *** 作。 this返回当前对象指针,*this返回当前对象(外面一般用引用承接) 一般都是const &的,因为基本都是仅仅用值
不修改返回值,且返回值为原对象。const class & 理由:返回原有对象,引用高效,const防止修改。
*** 作符重载
基本应用
核心应用场景:
将内建数据类型的 *** 作符 *** 作移植到自定义类里。比如int 和 Integer类
传入参数:
- 返回值作为临时量。const class 理由:一般用于返回新对象。不能传引用,因为临时量生存期很短。
- 修改返回值。class & 理由:返回原有对象继续修改。
- 重载中大量使用const 返回值,所以其成员函数,能const尽量const,不然 *** 作不了这个返回的临时量。
关于返回值:
返回值取决于我要返回什么,我要对我的返回值做什么。
- 遵循直观原则,一元就成员,二元就全局。
- 必须是成员函数且只能接受一个索引参数
- 很常用。
注意const成员函数:
- #
是否全局:
- include
- 特殊的,比如= () [] -> ->*,这种具有赋值和选择含义的,因为总是伴随对象,所以只能作为成员。
索引符:
- #include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; #
ifndef INTEGER_H std#
defineINTEGER_H //整数, *** 作符重载包装类
classInteger public :
Integer ( {
long=
0): ll i () <<"Integer c"<<ll;
{
cout } long getValue endl(
)
const //const函数主要是针对临时量的,否则(++a).getValue就无效return; } //全局函数,需要传入全部参数,给个friend
{
friend iconst
&
//传入a引用,返回a,所以全用const引用效率最高,能用const&最好
operator + Integer( const
& ) ;friend Integerconst a//二元写成全局更直观operator
+ ( Integer const
&,const& Integer) left; //成员函数,自己就不用传入了,不用friend Integerconst right//返回的是一个临时量,所以没办法用引用,注意是没办法才按值传递的。operator
-
( Integer )
return Integer (-
{
) ;//这里隐含返回值优化,可以直接将临时量创建在外面的承接空间中//从而省去内部量的一次构造和析构i}const&
operator
++
( Integer)
++ ;return*
{
ithis;
//返回当前对象引用的方法 }const//同理,因为返回临时量,所以只能按值传递 operator
++
( Integer int
) before(*this
{
Integer );//CC函数++;return;
i//返回临时量}
private before:long
;
*This
( i)
Integerreturn this;}
{
} ;const
&
operator+
( Integerconst
& ) return; Integer//传入引用,返回引用,不变 a}
{
const aoperator+
(
const Integer
& , const& Integer) leftreturn Integer Integer( right.
{
+ .)left;i } right#iendif//INTEGER_H//
int
main( void
) a(1)
{
Integer ;<<"a "<<.
cout getValue() a<<;<<"+a " << endl+
cout .getValue( ) a<<;<<"-a " << endl-
cout . getValue ( )a<<;<<"++a " << endl(
cout ++ ) . getValue(a)<<;<<"a++ " << endl(
cout ++ ) . getValuea()<<;<<"a++ " << endl.
cout getValue ( ) a<<;return0 ; endl}
必须返回一个对象,其也可以进行间接引用,这样保证了链式调用。 或者返回最终指向的类型,这就标志着到达终点。 #
include
operator->/operator->* 与迭代器嵌入
这个也只能是成员函数,这种形式,就意味着有类似于指针的行为,被叫做灵巧指针,常用于迭代器。
有两个规定:
- #include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; //
class Obj stdpublic
:
void f {
()
const <<++<< ;
{
cout } ivoid g endl(
)
const <<++<< ;
{
cout } jprivate : endlstatic
int
,;
//所有Obj公用这两个 } i; jint ::
=47
; Objinti :: =11
; Obj//容器j class ObjContainerpublic
:
void add {
(*
) //添加成员.Objpush_back obj()
{
a;}classobjSmartPointer;
//迭代器,用于访问,相当于将一些公共接口又封装了一次
friend ;//打开内部类向外 *** 作路径 class
SmartPointer SmartPointer//一般是嵌入,也可以选择全局+friendpublic
: SmartPointer {(
&)
:ocObjContainer( objc) ,index(objc0) <<"SmartPointer c"<<;
{
cout } //指针偏移,保证偏移不越界,空指针则通过返回值判断 bool endloperator
++
(
) if(++.
{
. size(index >= oc)a)//源代码有误,应该在这里先自增//再检查越界,否则后面还可能产生越界returnfalse ;
if
( .[
] ==ocNULLa)index//空指针 return false; return
true ;//不越界且有东西
} booloperator ++
(
int )returnoperator++(
{
) ;//结果相同}booloperator--
(
) if(--<
{
0 )returnindex false ;if
( .[
] ==ocNULLa)indexreturn false ;return
true ;}
bool operator--
(
int )returnoperator--(
{
) ;}//间接引用,返回一个成员*operator
(
)
Objconst if->(. [
{
] ==ocNULLa)index//唯一的可能意外,空指针 << "there is a Null Pointer in ObjContainer"<< ;
{
cout ; } return endl.[
]
; oc//额,实际上可以合并到这一步,空指针也没问题a}indexprivate:&
;
//用于绑定容器int
ObjContainer; oc//容器索引信息 }
; indexbeginSP (
)//做个小包装,隐藏掉this调用
SmartPointer returnSmartPointer(*
{
this );}private:<
*
;//容器仅容纳指针向量
vector}Obj;> aint main
(void
) constint=10
{
; [ sz ] ;;
Obj oforsz(int
ObjContainer oc=
0 ;< i ; ++) i . szadd i(&
{
oc[]);o}i::=.
beginSP
ObjContainer(SmartPointer sp)oc;//生成迭代器//下面这样也可以,只不过非常麻烦//ObjContainer::SmartPointer sp = ObjContainer::SmartPointer::SmartPointer(oc);dof
(
)
;
{
sp->g(); //->先左后右,左结合调用operator->,产生Obj*,右就是像一个正常指针
sp->}while(++
); returnsp0;}
初始化一个对象:将取右边的参数(只能有一个),去调用对应的C函数或者CC函数。 赋值一个已经初始化的对象:调用operator=,默认的是位拷贝,可以自定义。 #
include
->*目前感觉没啥用,所以就暂时不写了。
不重载. 号,因为影响太大了,妨碍了正常使用。
operator= 与赋值拷贝=号的行为有两种
- #include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; class
Fi public std:
Fi ( {
)}
};class {Fee
public:
Fee ( {
int)
<<"int"<<; {
cout } Fee ( endlint
,
float)<<"float" <<; {
cout } Fee ( endlconst
&
)<<"cc" Fi<<; {
cout } } ; endlint
main
(void
) =1;//初始化调用C/CC函数
{
Fee fee1 ; =; int
Fi fi=
Fee fee2 1 fi;
float a = 2;
// Fee fee3 = a, b; //可以看到,=初始化只能有一个参数 b return 0;
}
# include#
include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; class
Value public std:
Value ( {
int,
int,float aa) : bba ( cc) ,b(aa), c(bb)<< "c"<<cc;
{
cout } & //赋值是二元符,其实返回值没那么重要,但是这里返回一个引用,因为后面可能要改 endloperator
=
Value ( const
&)<<"operator=" Value<< rv;
{
cout = . ; endl=
a . rv;a=
b . rv;breturn
c * rvthisc;
} friend&//输出流的本质就是从左向右结合,传递,每次结合一个仍然是ostream类
operator
<< ostream( &
,const&ostream) osreturn << Value"a = " rv<<
{
. os << " b = " << rv.a << " c = " << rv.b << ; } rvprivatec : endlint
,
;float
; a} b;
int cmain
(void
) a(0,
{
Value 1,2) ,b (1, 2,3) ;<< "a "<<<<
cout ; << "b " a << endl<<
cout ; = ; b << endl"a "
a << b<<
cout ; return 0 a ; endl}
复杂带指针对象的赋值。这个要求把指针指向的东西进行递归复制与赋值 引用计数:优化operator=和CC函数的行为。赋值和拷贝构造时不新建空间,而是指向同一个空间,当计数为0,就销毁(有java那味儿了)。要修改对象时,如果有多个引用,那么就执行写拷贝,复制一份新的再写。 通过CC函数和operator=实现类之间的自动类型转换。
开辟内存,一般是调用malloc
常用的三种用法
- 调用构造函数
- 检查内存分配
- 调用析构函数。如果析构函数里有别的delete,可以实现递归调用,清楚所有相关对象
相当于加强版的malloc类/free类函数,将这些步骤封装起来。而且,可以重载,但是重载往往是用来优化加速的,一般使用默认的即可。
new和delete让动态内存管理更加方便,从此数组是路人,二级指针闯天下。
new:
- 释放内存,一般是调用free
- 建议在delete p后加上p=NULL补刀,防止二次delete同一片区域
- delete void*不建议这么做,因为不会调用析构函数,极易引起内存泄漏
delete:
- #include#include
#include
// 系统库,System调用相当于cmd#
include//c++级别字符串 #
include//c++级别数组 #
include//文件读写 #
include//调试 using
namespace; //注意,我们这里还是使用了void*,需要写好析构函数
# ifndef stdPSTASH_H
#
definePSTASH_H class
PStashpublic :
PStash ( {
):
quantity(0 ),storage(0) ,next(0) <<"c"<<;
{
cout } ~ PStash endl(
)
;//因为有循环,不内联intadd( void
* )constint= element10
{
; if inflateSize ( )//检查扩容
inflate (next >= quantity);
{
}[inflateSize++]
=
storage;next//指向传入的元素return ( element-1
) ;next //返回下标 }void*operator
[
]( int)constif( index< 0
{
|| )index return NULL ; index >= next}
{
return []
;
} storagevoidindex*remove
(
int) void*= indexoperator
{
[] v ( );if(index!=NULL
) //确定有东西,在storage中置零指针v [ ]= NULL
{
storage;index//注意!这里没有清理对象,而是将清理任务扔给了客户 } return;}
int
count v(
)
const return;} private
{
: nextint
;
//上限int
; quantity//下一个索引,也是当前的数量值void
* next*;
//storage实际上是指针数组,只不过不限制大小了voidinflate storage(int
) constint= increasesizeof
{
( void psz * );void**=
newvoid* new_s [ + ]; memsetquantity ( increase,0
,(new_s+ )* )quantity ; increasememcpy ( psz,,
*)new_s; storage+= quantity ; pszdelete[
quantity ] increase;
=;} storage}
storage ; new_sPStash
::
~PStash
()for(int=
{
0 ;< i ; ++) i if next( i[]
{
!= NULLstorage)i<< "PStash not cleaned up" <<;
{
cout } } delete endl[
]
;
//对storage指针数组中每一个指针调用delete}# storageendif//PSTASH_H//
int
main( )
; for(int
{
PStash intStash=
0 ;< i 5 ;++ i ) .add i(new
{
intStashint()) ;//内建类型也可以使用new,快速生成空间和指针i}for(int
=
0 ;< i . count( i ) intStash;++)//要强制转换类型还是很麻烦,后面模板可以解决<< i"intStash["<<
{
<<
cout "] = " << * i ( int * )[]<<;intStash}ifor ( endlint
=
0 ;< i . count( i ) intStash;++)delete( iint*
{
) .remove()intStash;//remove去掉指针,delete将指针指向的内存释放//int*其实不用加,加了保险i}in(
"input.txt"
)
ifstream ;;;while(
PStash stringStashgetline
string line(
, )).inadd line(new
{
stringStashstring()) ;}linefor(int
=
0 ;< i . count( i ) stringStash;++)<<"stringStash[" i<<<<
{
cout "] = " << * i ( * ) []string<<;stringStash}ifor ( endlint
=
0 ;< i . count( i ) stringStash;++)delete( i*)
{
. removestring()stringStash;}returni0;
}
自己写
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)