您的位置:首页 > 编程语言 > C语言/C++

C++中关于流以及参数传递形式的解析和使用。

2016-08-19 17:38 387 查看

前言

本文讲述了参数传递中引用形参和非引用形参的区别,并且对哪些情况使用使用什么样的参数传递给出了说明。并且讲述了关于指针函数的了解和使用方法。在文章中还讲述了关于C++中流对象的特点和操作,包括文件流,控制流,字符串流以及文件的打开模式。

1、new和delete表达式

new和delete表达式不仅可以动态创建、撤销数组,而且可以使用于单个对象。用于创建单个对象的方法如下:

int *pa=new int;

int *pb=new int();

string *pc=new string;

new的作用是创建一个新的内存空间用于存放指定类型的数据并返回一个指向该内存地址的指针。那么pa和pb所指向的内存有什么不一样呢。在于值得初始化。如果是内置类型,没有调用()进行值初始化,值默认是没有定义的。如果是类类型没有调用()进行值初始化,则会调用默认的构造函数进行初始化。所以这里pc指向的对象是有一个空值的。但如果内置类型调用了()进行初始化,则会初始化为0。

如果创建了一个对象而不去释放它,久而久之就会造成内存泄漏了,所以要养成释放内存空间的习惯。释放空间使用的是delete。如下:

delete pa;

此关键字会删除pa指向的空间。要注意用delete使用的指针如果不是通过new返回的,这样的操作是没有定义的,是不合法的。当一个原来有值得指针被释放了以后就变成了一个悬垂指针,为了避免造成二次释放的错误,所以要养成一个好习惯,就是释放后的指针要设为0值。

pa=0;

2、非引用形参

定义函数的形参可以分为引用形参和非引用形参,每次函数激活的时候,都会重新创建形参,如果是非引用形参就将实参的副本赋值给它。如果是引用类型的形参,则形参只是实参的别名,因此对非引用类型进行修改不会影响实参的值,而对引用形参修改则会影响实参的值。

2.1 指针形参

指针形参也只是指针实参的一个副本,因此对指针形参的修改不会影响实参对象,但是如果是修改指针形参所指对象的值(前提是,指针形参的值未改变),则实参所指对象也会被修改。如果指针形参所指的数据类型是const类型,则实参的值既可以是const类型也可以是非const类型。但如果指针形参所指的数据类型是非const类型,则只能传递指向非const类型的实参。

2.2 const形参

如果一个函数的非引用类型是const形参,则传递给他的实参类型可以使const也可以是非const。前面说到,实参给非引用形参传递的只是副本,也就是或形参和实参是没有关系的,因此无论实参是否const类型,形参都是独立的,和实参并无关联。这里需要提醒的是指针形参,分为两种,const类型的指针形参,还有就是指向const对象的指针形参,如下:

int get(const int *param)
int get(int const *param)

第一种指针形参表示指向的对象是const类型,因此它的实参指针只能是指向const类型的指针。第二种是const指针,这种表示实参指针指向的对象既可以是const类型也可以是非const类型,因为形参指针和实参指针不是同一个对象,只是一个副本,对指向的对象是否const并未要求。

2.3 非引用形参的不适用场景

1、不适合需要在函数中修改形参达到修改实参的目的的场景。

2、不适合传递占据大容量数据对象的场景。

3、引用形参

引用形参是实参的一个别名,对形参的使用等于是对实参的使用。引用形参声明如下:

void swap(int &a,int &b);

通过给引用形参传递实参对象来初始化引用形参,则可以达到通过修改引用形参而影响引用实参的目的。同时,利用引用形参的特点,可以是间接达到让函数返回多个值的目的。如果传递的实参是指针,形参是指针的引用,怎么定义呢?如下:

void  swap(int *&a,int *&b)

可以从右往左结合理解,&a是一个引用,int *表示该引用是一个指向int类型的指针。



4、传递大型容器的数据对象

大型容器的数据对象含有的数据量太大,不适合用非引用的方式传递,可以用引用的方式传递。但是更建议传递迭代器的方式。比如传递分别标志开始和结尾的迭代器,之后通过迭代器操作容器对象。

5、数组形参

在c++中使用数组形参含有两个特殊的性质,一,数组是不能复制的,二,传递数组的时候会自动转换成数组的首元素地址。考虑如下三个函数声明:

void printvalues(int *p);
void printvalues(int a[]);
void printvalues(int a[10]);

以上三个方法都是等价的,因为数组是不能复制的,所以都会自动转换成首元素地址。通过非引用形参的方式传递数组,编译器是不会检查数组长度的,因此必须严格控制访问界限。当然也可以用应用的方式传递数组,如下:

void printvalue(int (&a)[100]);

通过引用传递的方式,编译阶段就会检查数组长度,从而确保不会越界访问。

6、函数的返回值

函数的返回类型只有两种,一种是void,另外一种是其他。前者不要求返回值,可以直接掉调用return停止运行。后者则要求必须返回值。

6.1  main函数的返回值

main函数的返回值是0和非0值,0表示正常退出,非0值根据不同的机器有不同的意义。

6.2 返回非引用类型

如果返回的类型是非引用类型,和传递形参一样,返回的只是函数内的剧本对象的一个副本,并不是该对象本身,该对象在执行完毕之后便会销毁。

6.3 返回引用类型

返回引用类型是将该对象本身返回,并不是返回它的副本。因此千万不要返回局部对象的引用。因为一旦函数执行完毕,局部对象就会被释放,这会导致返回的引用绑定的对象是个不确定的值。

7 内联函数

在调用函数的时候是需要很大的开销的,如果将函数声明为内联函数的话,会提高效率。内联函数一般只适用只有几行代码的函数,不适用于大量代码的函数。内联函数的定义如下:

inline string get()
{
string s("happy");
return s;
}

在调用处

cout<<get()<<endl是等同于cout<<s<<endl;

因为内联函数对于编译器而言必须是可见的,因此,必须在头文件中定义内联函数,而不是声明。



8 直接初始化和复制初始化

对于类类型来说,直接初始化是直接调用与参数匹配的构造函数,复制初始化则是先调用指定构造函数创建一个对象,然后再将该对象通过复制构造函数将其复制给正在创建的对象。

9 类的成员函数

类的成员函数既可以在类内定义也可以在类外定义,在类内定义的会被隐式声明为内联函数。每个函数都有一个隐含的参数this指针,它在每次函数被调用的时候会初始化为调用该函数的对象的地址。如果有一个函数声明如下:

int add(int a ,int b) const;

则意味着this是一个指向const类型的指针,即通过this只能读取数据而不能修改数据。实际上,再使用成员函数的时候,任何不加前缀的成员变量都会隐含一个this指针。比如:

void  set(int a)
{
b=a;
}

这里假设b是函数的成员变量,b=a这句话等价于this->b=a;

在类外定义成员函数必须声明该函数属于哪个类,即说明命名空间。

10  类代码文件的组织

作为编写c++程序的一个好习惯,将声明和定义分离开来是必须的。比如定义一个类,名字是type,则将定义该类的文件命名为type.h。对于类的成员函数,声明应该放在头文件里,定义放在源文件中。这样任何需要使用type类的源文件只需要包含type.h头文件即可。

11  指向函数的指针

指向函数的指针不同于指向变量的指针,函数指针是指向函数对象的。函数指针也有自己的函数类型的要求,因为函数是由返回值类型和形参表确定,因此函数指针也是。下面是一个声明函数指针的方法:

int (*p) (int a,intb);

这里将p声明为一个函数指针,它指向的函数类型是返回int型需要两个int参数的函数类型。注意(*p)的括号不可缺。为了方便使用,常常使用typedef声明:

typedef int (*p) (int a,in b)

假如定义了一个函数如下:

int swap(int a,int b);

则让p指向该函数的方法是:

p=swap或者p=&swap。注意函数指针必须赋值了才能使用,除了函数地址外,也可以给0值,此时在此指针上的任何操作都是没意义的。通过函数指针调用函数的方法:

p(2,3)等同于swap(2,3)。同时,函数指针也可以做形参。首先声明一个带有函数指针的形参:

void get(int a,int (*) (int c,inb));或者是 void get(int a, int (int c,int b))。

如果是将指针函数作为返回值,读者要注意了,前方高能,因为这种写法很难理解,比如:

int (*swap(int ,int)) (int c,int d);

理解这种写法要从内往外看。首先声明了一个swap函数,它需要两个int参数。它的返回值是int (*) (int c,int d),即返回一个返回值是int并带有两个int形参的指针函数。

12  标准输入输出流

c++标准库中含有很多与输入输出有关的标准类,它们基本包含在iostream,fstream,sstream者三个头文件中,每个头文件含有的对象分别记起作用如下:



这些对象之间都是有关联的,继承关系如下:



其中ostream,istream,iostream是用于操作控制台的,ofstream,ifstream,iofstream是用于操作文件对象的,ostringstream,istringstream,iostringstream是用于操作字符串对象的。这些都是支持char字符的,如果是宽字符wchar_t类型,则有wostream,wistream,wiostream(这三者也是包含在iostream头文件中)以此类推。

虽然这些对象都是标准库的类,但是它们和一般的类不同,他们有不可复制的特性。因此它们之间不可以通过赋值互相传递,并且如果是作为一个函数的形参的话,该形参必须是指针或者引用对象,其余任何操作都是不对的。

2.1 检测输入流输出流的状态

标准库头文件中定义了一些成员常量,用于标识当前输入流输出流所处的状态。它们都是type::iostate类型的常量。比如istream类型中,会含有istream::iostate类型,并含有三个常量istream::failbit,istream::badbit,istream::eofbit。分别表示,当前输入流处于失败状态,此状态是可以通过程序来修复的。当前输入流处于错误状态,此状态不可修复。当前输入了处于文件末尾状态,此状态表示读取的内容达到了文件结束符。同样的道理,ostream,iostream,ifstream,ofstream,fstream......都含有者三个常量。

除了常量之外,还有一些方法来读取当前的状态,eof()用于判断当前流是否达到文件结束符,bad()判断当前流是否被破坏,fail()判断当前刘是否失败。上面的三个如果返回true表示是。good()用于判断当前状态是否可用,返回true也表示可用。具体的描述如下:



结合上面的描述,给出下面的例子:

/*
条件状态
*/
int val = -1;
istream::iostate state = cin.rdstate();
while (cin >> val,!cin.eof())
{

if (cin.bad())
{
throw "输入流被破坏";
break;
}
if (cin.fail())
{
cout << "输入的不是整数" << endl;
cin.clear(istream::failbit);
break;
}
cout << "输入成功" << endl;

}
cin.setstate(state);


其中state变量是用来保存流的状态,后续可以通过setstate方法恢复。这里的循环使用了逗号表达式。逗号表达式的意思是,它只返回右操作数的结果,因此这里等同于while(!cin.eod())。下面的就是判断流的状态。当前读入的数据不可以转换为指定类型的时候,就会处于fail状态,如果处于破坏的状态则抛出异常。

2.2 输入输出流的缓冲

每个输入输出流都有自己的缓冲区域,因此所读取或写出的数据不会立马刷新。即不会立刻写到目标的文件或者硬件设备中。但是,有以下几种情况会导致流的刷新。

①、缓冲区满了,在读入下一字符的时候就会刷新。

②、正常执行完了main函数。

③、显示调用ends,endl,flush进行刷新。

④、给io对象设置unitbuf操纵符,这样就会在每次读取数据的时候刷新。

⑤、当与ostreamt对象关联的istream对象进行都操作的时候,ostream对象的缓冲区也会刷新。

考虑一下:

cout<<"hi"<<endl;
cout<<"hi"<<ends;
cout<<"hi"<<flush;

以上三种方法都会导致缓存被刷新,第一种会在后面添加换行符,第二种添加一个null字符,第三种直接刷新,不添加任何字符。

再看:

cout<<unitbuf<<"happy"<<"day"<<nounitbuf;

它等同于cout<<"happy"<<"day"<<flush;

unitbuf操纵符会导致读入的数据会马上刷新。nounitbuf使iostream对象恢复正常。

再看:

cin.tie(&cout)

iostream对象含有一个tie方法,此方法含有一个ostream的指针形参。这样就可以将cout和cin关联起来,这之后,任何在cin的操作都会导致cout的缓冲区被刷新。可调用cin.tie(0)解除绑定。注意不可以重复绑定。

2.3 文件流对象的创建

fstream,ifstream,ofstream都是有关文件操作的流对象,这些对象含有的基本操作和cin,cout等基本一样,但是也拥有特殊的地方,比如,它拥有open,close方法。在使用文件流对象之前,必须先绑定文件对象,才可以对文件流对象进行操作。绑定文件对象有两种方式:

ifstream istream("in");
ifstream istream;
istream.open("in");

一种是直接初始化的方式,另外一种是先声明一个对象,然后调用open打开。注意,一旦文件流和文件关联了起来就不可以再另外绑定其它文件了。如果要想重新绑定文件,必须先调用close方法关闭文件,然后再调用clear方法清除文件状态。和cin对象一样,在对文件流进行操作之前,可以简单的调用if对文件流进行判断,看看是否可用。

典型的用法:

ifstream fstream("D://text.txt");

if (fstream)
{
string s;
while (fstream >> s)
{
cout << s << endl;
}
fstream.close();
fstream.clear();
}


2.4 文件的打开模式

上述用到的初始化和open方法打开文件其实包含一个默认的文件模式。以ifstream来说,默认的是in模式。下面给出有关文件模式的常量:



各模式起到的效果,in打开一个文件输入流并将光标定位在文件起始部分。out打开一个文件输出流,并清空该文件本身包含的内容。app打开一个问价输出流,并将光标定位在文件末尾处。ate打开文件并定位在文件末尾处。trunc打开文件输出流并清空里面的内容。binary以二进制的形式操作数据,不进行翻译的方式打开文件流对象。其中in只能用于文件输入流,out只能用于文件输出流,app和trunc也是。ate,binary两者都可以使用。如果是fstream对象,上面的文件模式都可以使用。默认的fstream对象使用的是in和out组合模式。即对fstream绑定的文件,可进行都操作,也可以进行写操作。使用文件模式的用法:

ifstream infile("in.txt",ifstream::in);

ofstream outfile("out.txt",ostream::out);

fstream inOutFile("xx.txt",fstream::in|fstream::out);

可以发现,文件模式是可以组合的。常用组合形式:



下面给出一个标准的打开文件的函数:

/*
标准的打开文件流的函数
记住,流的参数传递和返回值不可以使用赋值的方式,只能是引用传递或者指针
*/
ifstream* openFile(ifstream *inFile, const string &fileName)
{
//由于不清楚文件流原本的状态,所以清除可以确保打开成功。
inFile->close();
inFile->clear();
inFile->open(fileName.c_str);
return inFile;
}




2.5 string对象的输入输出流

C++中同样为string对象定义了相应的操作流,基于此操作流,可以方便的对string对象进行读写。有关string对象的流油istringstream,ostringstream,iostringstream。上述几个流都是iostream的子类,所以也包含基本的读写方法。特殊的地方在于,string流对象含有一个可以包含string对象的构造函数以及一个str方法。如下:



例子如下:

string line, word;

while (getline(cin, line)&&line!="endl")
{
istringstream iString(line);
if (iString)
{
while (iString >> word)
{
cout <<unitbuf<< word ;
}
cout << nounitbuf;
}
}


---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767-----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐