(一〇三)引用变量及相关(万字长文)-阿里云开发者社区

开发者社区> 开发与运维> 正文

(一〇三)引用变量及相关(万字长文)

简介:

****本篇大概近万字****


格式:

int a = 1;

int &b = a; //b作为a的别名,是一个a变量的引用,需要在声明时进行初始化

 

效果:

ba的引用变量,b的值和地址,和a是相同的,且不会改变;

 

②在声明引用变量b时,需要同时对变量b进行初始化,即不能先声明,然后再初始化;原因是引用变量b需要根据他被引用的变量,而拥有地址,而不是有自己独立的内存地址;

 

③引用变量的地址,由初始化时的变量决定,而不是由最近一次赋值而决定。例如ba变量的引用,然后又有变量c将值赋给b,那么b的地址依然是a,而不是c

 

④引用变量其在声明的时候,变量名前的“&”是类型标识符的一部分,而不是地址运算符。

在之后在引用变量名前加上“&”,才是地址运算符;

 

⑤引用变量的类型,需要和被引用的变量的类型相同。即aint类型,那么引用变量b也应该是int类型;

 

⑥引用变量和指针不同。

指针是指向某个内存地址,而引用变量是变量的别名。

指针有其储存自己的内存地址(即指针指向的地址,是储存指针的内存地址的值),而引用变量的地址,就是被引用的变量的内存地址。

 

⑦可以将引用变量理解为变量的另外一个名字,例如上面代码中的ab,在很多地方的使用方式是相同的。只不过引用变量还有自己独特的使用范围。

 

如代码:


#include<iostream>

int main()
{
	using namespace std;
	int a = 1;
	int &b = a;	//b作为a的别名,是一个a变量的引用,需要在声明时进行初始化
	cout << a << "," << b << endl;	//输出a、b的值
	cout << &a << "," << &b << endl;	//输出a、b的地址
	cout << endl;
	int c = 2;
	b = c;	//将c的值赋给b,注意,这不是让b成为c变量的别名。
	cout << c << "," << &c << endl;
	cout << a << "," << b << endl;
	cout << &a << "," << &b << endl;
	system("pause");
	return 0;
}

输出:


1,1
003EFEC0,003EFEC0

2,003EFEA8
2,2
003EFEC0,003EFEC0
请按任意键继续. . .

总结:

①从上面的代码中可以看出,ba的地址、值是相同的;

 

②在中间b=c,然而b并没有和c的地址保持相同,而是和a的地址依然保持相同,但b的值、以及a的值,和c的值相同,相当于将c的值,赋给了ab

 

③当引用变量或者被引用的变量的值改变时,另外一个值也一样被改变(因为实质上是一个值有两个变量名);

 

④可以一个变量有多个引用变量。如在之前代码的基础上,加上int &d=a或int &d=b; 然后d的地址和值,也将和ab保持相同;

 

⑤必须声明引用变量的时候进行初始化。

 

 

 

 

引用变量作为函数参数时:

引用变量作为函数参数时,可以像指针那样直接修改变量的值,但是具体方式不同。

 

使用指针修改实参时,传递的实参应为变量的地址,在函数内部,应该如同使用指针那样,通过地址进行操控值;

 

例如代码:

//使用指针修改变量的值
#include<iostream>

int chenger(int*);	//传递的参数应为指针

int main()
{
	using namespace std;
	int a = 1;
	int b = chenger(&a);	//调用函数时,传递的是变量的地址
	cout << a << ", " << b << endl;
	system("pause");
	return 0;
}

int chenger(int*m)
{
	*m *= 2;	//用指针的方式,修改指针指向的值
	return *m;	//返回应为指针加上接触运算符*
}

使用引用修改实参时,传递的值是变量自己,参数是引用变量的标识符“&”,在函数内部,像使用变量那样进行修改。

如代码:

//使用引用修改变量的值
#include<iostream>

int chenger(int&);	//传递的参数为变量的别名

int main()
{
	using namespace std;
	int a = 1;
	int b = chenger(a);	//调用函数时,传递的是变量本身,而非加上地址运算符的&a
	cout << a << ", " << b << endl;
	system("pause");
	return 0;
}

int chenger(int&m)
{
	m *= 2;	//用修改变量的方式修改变量
	return m;	//返回值为变量名或者相关的表达式
}


二者的效果是相同的。输出的内容都是“2,2

 

 

 

引用变量和指针作为函数的参数时的差别:

①参数的形式不同:引用变量是运算符&,指针是*

 

②传递的内容不同:引用变量传递的是值,指针传递的是地址;

 

③使用方式不同:引用变量像使用变量一样使用,指针需要按指针的方式进行使用;

 

④方便程度不同:引用变量在某些时候,由于不用加解除运算符“*”,在操纵值的时候更简单;

 

⑤都能直接对参数进行修改,而直接按值传递(不使用引用变量或者指针时),修改的值是实参的副本(形参)。

 

 

 

 

引用变量和常量作为参数时:

①函数原型和函数头中的参数不同;

 

②在调用函数时,引用变量的参数通常不能是表达式(而是变量名)(使用const作为参数的限定除外),而常量作为参数时,可以是表达式;

如int A(int&);  中,

参数可以这么写:A(a)——a为变量名,

但不能这么这么写:A(a+1)  或  A(a+b)——b也为变量名

 

③在使用引用变量作为参数时,假如需要不能修改引用变量的值,那么应该使用常量引用,即在函数头之前加const进行限定(如果被修改了,编译器会提示出错);

 

④对于基本类型的传递,假如需要限定不能被修改,那么不如使用按值传递的方式(即让函数在调用时,创建变量的副本);

 

⑤对于数据比较大(如结构和类)时,引用参数将会很有用。

 

 

 

临时变量:

之前提到,使用引用变量的函数,通常在调用的时候,参数不能是表达式。

 

但是在使用const进行限定时,则可以在调用的时候使用表达式,如:

int chenger(const int&);

int b = chenger(a+1);

在这两行代码中,第一行是函数原型,使用引用变量,但同时使用了关键字const。于是,在调用时,就可以使用表达式a+1

 

但同样,因为关键字const的限定,因此变量a在函数chenger中,并没有像普通引用变量那样被修改。

 

 

如果实参与引用参数不匹配,C++将生成临时变量

 

当前,仅当参数为const引用时,C++才允许这么做(如上)。

 

在引用参数是const的情况下,编译器将在两种情况下生成临时变量(早期的C++则不使用const也会生成临时变量)

①实参类型正确,但不是实参不是左值;

②实参类型不正确,但可以转换为左值(例如long转换为int);

 

左值:左值参数是可以被引用的对象。例如:变量、数组成员、结构成员、引用、和解除引用的指针。

非左值包括:字面常量(但不包括用引号括起来的字符串,如"abcde"这样的字符串,因为这种字符串由其地址表示,即可以在字符串前加&输出地址),还有包括多项的表达式(比如a+b)。

另外,常规变量和非常规变量都可以是左值,只不过前者可以被修改而后者不能。

 

 

由于使用引用变量的意义在于想要修改引用变量的值(不然就直接使用按值传递的方式了)。而在早期,由于函数在需要引用变量作为参数时,遇见非左值(例如表达式等),会创建临时变量。而使用临时变量时,那么参数变量就不会被修改,违背了使用引用变量的初衷。

 

因此,现在只有当使用const关键字的时候,才允许创建临时变量(因为使用了const则无法修改参数)。

 

 

 

创建临时变量的几种情况:

假如有函数:

int square(const int &a)

{

std::cout << &a << std::endl;

return a*a;

}

那么,以下几行代码有的会创建临时变量,有的则不会。

 

代码:

//临时变量的创建
#include<iostream>

int square(const int &a);

int main()
{
	using namespace std;
	int ma = 1;	//int变量
	double mb = 1.1;	//double变量
	int &b = ma;	//引用变量
	int*c = &ma;	//指针

	int a[6];	//数组
	cout << &ma << endl << endl;
	a[1] = square(ma);	//不会创建临时变量
	a[4] = square(b);	//不会创建临时变量
	a[5] = square(*c);	//不会创建临时变量
	a[0] = square(1);	//会创建临时变量
	a[2] = square(mb);	//会创建临时变量
	a[3] = square(ma + 1);	//会创建临时变量


	system("pause");
	return 0;
}


int square(const int &a)
{
	std::cout << &a << std::endl;
	return a*a;
}

输出:

0024F9F4

0024F9F4
0024F9F4
0024F9F4
0024F8E0
0024F8D4
0024F8C8
请按任意键继续. . .

结论:

①我们可以发现,在不会创建临时变量的几种情况下,输出函数参数a的地址,是相同的——原因在于创建的是引用变量,而引用变量的地址和值,和被引用的变量的地址和值是相同的。

 

②而在后三种情况下,a的地址和值是不同的,且互相之间也不同,原因就在于创建了一个临时变量,临时变量有着属于自己的内存地址。

 

③创建临时变量的情况:常量、表达式、类型不符合参数要求但可以被转换,这三种情况,会创建临时变量。

 

④在不需要修改实参的情况下,应当尽量在引用形参时使用const,理由有三个:

1)使用const可以避免无意中修改数据的编程错误;

2)使用const使函数能够处理const和非const实参,而不使用则只能处理非const数据;

3)使用const能够使函数正确的生成并使用临时变量(可以避免因为使用引用变量而导致在函数调用时,需要注意使用正确的参数类型);

 

 

 

 

右值引用:

之前提到的引用变量,通常称为左值引用。

 

所谓左值,能在作为左值参数时,可以作为被引用的对象。比如变量、字符串、数组、结构、指针、引用等。一般是放在赋值符号的左边。

 

而右值,就是一般可以放在赋值符号右边的内容,比如字面常量(如1),含有多项内容的表达式等(比如a+b)。

 

引用变量在使用的时候,例如int &b=a; 这样的是可以的,作为函数参数时,调用函数square(a); 这样的也是可以的。但如果是int &b=a+1; 或者是 square(a+1)这样的是不行的。

 

而右值引用,则是可行的。右值引用需要使用“&&”这样的作为类型标识符的一部分。例如:int &b=a+1;  又或者是int c=square(a+1);这样。

 

注释:(以下都不太懂,或者不确定)

①按照说明,右值引用用来实现移动语义;

 

②右值引用时,无论是作为参数还是赋值时,都不能是左值——实际编辑代码时发现,变量、字符串(这个不会用)、(其他未试验)。不能当右值。

例如:int &&b=a; 会提示错误,而int &&b = a+1; 则可以正常使用。

在调用函数时,参数也应该为类似的表达式,或者字面常量,例如square(a+1) 或者是square(1) 是可以的(不需要使用const 进行限定)。

但右值引用在函数中,似乎也不会像引用变量那样更改变量的值。

 

 

 

将引用用于结构:

与基本类型(如intdouble等)相比,引用更适合用在结构和类上。

 

和基本类型的用法相同,希望修改传递的结构变量,则不使用const;希望不能修改传递的变量,则使用const进行限定。

 

在结构中使用引用变量的优点

①和按值传递(创造结构副本)相比,节省了更多内存(因为创造副本需要占用内存);

也可以修改或者不修改原有的值(根据不同情况决定是否加入const进行限定);

 

②与使用指针相比,更简单直接和方便(无需通过指针专有方式进行操作);

 

③需要返回值时,也可以像使用指针那样,返回值是引用变量(在函数原型的函数名之前加类型标识符“&”);

 

 

如代码:

#include<iostream>
#include<string>

struct player
{
	std::string name;
	int str;
	int hp;
};

player& power_up(player&);	//函数原型,在函数名之前加上类型标识符&
void show(const player&);
player& together(player&, const player&);

int main()
{
	player man[4]=
	{
		{"张三",10,50},
		{"李四", 12, 45},
		{"王五", 15, 40},
		{"赵六", 20, 30}
	};
	show(man[0]);	//显示man[0]属性
	power_up(man[0]);	//提升man[0]
	show(man[1]);	//显示man[1]
	together(man[0], man[1]);	//将man[1]的值赋给man[0]
	std::cout << "这是合体后的属性:" << std::endl;
	show(man[0]);	//显示赋给之后的
	std::cout << "这是合体后的属性:" << std::endl;
	show(together(man[0], man[2]));	//将man[2]的值赋给man[0],然后显示together的返回值man[0]
	show(together(power_up(man[0]), man[3]));	//将man[0]强化(调用power_up函数),然后将man[3]的值赋给man[0](调用together函数),再将man[0]的值显示出来(调用show函数)
	
	player &man_2=power_up(man[1]) = man[0];	
	//由于power_up函数的返回值是引用变量,因此可以将man[0]的值直接赋给man[1]作为参数时的power_up函数,
	//而power_up的返回值是man[1],因此man[1]的值和man[0]相同,且man_2成为了man[1]的形参(而不是man[0])的
	std::cout << &man_2 << std::endl;
	std::cout << &man[0] << std::endl;
	std::cout << &man[1] << std::endl;

	system("pause");
	return 0;
}

player& power_up(player&men)	//像使用按值传递那样编写代码
{
	men.str += 2;
	men.hp += 5;
	std::cout << men.name << " 被强化了!" << std::endl;
	show(men);
	return men;
}

void show(const player&men)	//输出结构的成员
{
	using std::cout;
	using std::endl;
	cout << "——————————" << endl;
	cout << "名字:" << men.name << endl;
	cout << "力量:" << men.str << endl;
	cout << "血量:" << men.hp << endl;
	cout << "——————————" << endl << endl;
}

player&together(player&men_1, const player&men_2)	//men_1结构各项的值是men_1和men_2之和
{
	std::cout << men_1.name << " 接受 " << men_2.name << " 的合体" << std::endl << std::endl;
	men_1.name += men_2.name;
	men_1.str += men_2.str;
	men_1.hp += men_2.hp;
	return men_1;
}

输出:

——————————
名字:张三
力量:10
血量:50
——————————

张三 被强化了!
——————————
名字:张三
力量:12
血量:55
——————————

——————————
名字:李四
力量:12
血量:45
——————————

张三 接受 李四 的合体

这是合体后的属性:
——————————
名字:张三李四
力量:24
血量:100
——————————

这是合体后的属性:
张三李四 接受 王五 的合体

——————————
名字:张三李四王五
力量:39
血量:140
——————————

张三李四王五 被强化了!
——————————
名字:张三李四王五
力量:41
血量:145
——————————

张三李四王五 接受 赵六 的合体

——————————
名字:张三李四王五赵六
力量:61
血量:175
——————————

李四 被强化了!
——————————
名字:李四
力量:14
血量:50
——————————

004EF8A4
004EF880
004EF8A4
请按任意键继续. . .


总结:

①因为string类可以相加,因此使用string类。如果是C-风格字符串,就相对要麻烦一些;

 

②使用std::cout和std::endl ,或者是using std::cout之类替代了using namespace std

 

together函数,将第二个参数的值,加在第一个参数之上(即第一个参数的值是第一个参数和第二个参数之和);

 

④因为是引用变量,因此无需像指针那样使用“->”运算符,可以直接使用逗号运算符即可。

 

⑤需要返回值时,使用类型标识符“&”在函数名之前,可以让返回的是引用变量,而非创造的副本。

 

(这两个的区别貌似是引用变量占用内存更小,效率更高。但前提是返回的是函数参数,或者是new的值,或者是static限定,或者是全局变量等,不然会随着函数的消亡而无法指向)

 

另一个原因是,返回引用,则可以将函数放置于赋值符号“=”的左边时,赋值符号右边的值将可以修改引用变量;否则,将无法修改。

例如:player power_up(player&);和player& power_up(player&);这两个函数,

在遇见代码:power_up(man[0])=man[3];时

前者man[0]的值没有被改变,而后者man[0]的值被改变了。

 

在返回值遇见指针时,

若返回的是引用变量,则指针可以指向被引用的变量;

若返回的不是引用变量,则指针指向的是创建的副本。

 

⑥show(together(power_up(man[0]), man[3]));

这行代码,相当于

power_up(man[0]);

together(man[0],man[3]);

show(men[0]);

这三行代码依次执行的效果。

由于power_up函数和together函数都有返回值(返回的是引用变量),因此可以使用。

 

假如power_up(man[0])函数没有返回值,那么就需要将代码修改为:

power_up(man[0]);

show(together(man[0],man[3]);

这样的形式。

 

 

player &man_2=power_up(man[1]) = man[0];这行代码中,man[0]的值被赋给了power_up(man[1])函数的返回值(本函数返回值是引用变量,man[1]),于是,man[1]的值与man[0]相同;

 

又因为man_2在power_up函数的左边,因此man_2成了函数返回值的引用变量(即man[1]的引用变量);

 

因此man_2的地址,和man[1]的保持一致,又因为man[1]并不是man[0]的引用变量,因此man_2和man[0]的地址是不同的。

 

于是,修改man_2实际上就是修改man[1](但不是修改man[0]),man_2是man[1]的引用变量。

 

 

⑧返回值是引用变量时,应避免返回的是函数结束后就不再存在的变量(例如自动变量,局部变量,未使用static关键字的变量或非全局变量之类);

因为引用变量是被引用的变量的别名,假如被引用的变量随着函数的终止而消亡,那么引用变量自然也不存在了。

因此,最简单的来说,返回值是函数的一个参数(可能在函数内部被修改或者未被修改)。

同样,也应避免返回临时变量的指针(同样也是因为临时变量消失了,指针无法指向消失的变量);

 

⑨函数返回引用变量时被const限定:

可以避免像power_up(man[0])=man[3];这样情况的发生;

可以在函数的参数需要是const限定时,直接使用,例如;

const player& power_up(player&men)在调用时,直接放于void show(const player&men)函数的参数之中。

如代码:show(power_up(man[0]));这样。

 

 

 

将引用对象用于类:

就像用于基本类型那样使用,但类也有自己特殊的功能,比如string类的相加。

 

另外,返回值是类或者类的引用变量,其结果是不同的。不能任意使用引用变量,如代码:

//返回值是变量、引用变量之间的差别
#include<iostream>
#include<string>
using namespace std;

string v1(string&, const string&);	//返回值是创建变量的副本
const string &v2(string&, const string&);	//返回值是引用变量(被引用的是函数的参数)
//string &v3(string&, const string&);	//返回值是创建变量的引用变量(但该变量在函数结束时已经消亡),因此无法使用
void show(const string);

int main()
{
	string main, copy, result;
	main = "要自信";
	copy = main;

	result = v1(main, "***");
	show(result);
	show(main);
	cout << endl;

	result = v2(main, "@@@");
	show(result);
	show(main);
	cout << endl;
	
	main = copy;
	//下面这行代码会出错,原因见函数v3右边的注释
	//result = v3(main, "+++");
	//show(result);
	//show(main);
	//cout << endl;

	system("pause");
	return 0;
}

void show(const string a)
{
	cout << a << endl;
}

string v1(string&main, const string &another)	//another参数使用const进行限定的原因在于,只有这样,才能在某些时候,生成临时变量
{
	string a;
	a = another + main + another;
	return a;
}

const string &v2(string&main, const string&another)	//理由同v1函数,在这里,返回的是main的引用变量,而不是main的副本
{
	main = another + main + another;
	return main;
}

//string &v3(string&main, const string&another)	//在v3函数中,由于返回的是函数内部创建的临时变量的引用,因此当函数消亡时,返回的引用变量就无法使用了
//{	string a;
//	a = another + main + another;
//	return a; }

输出:


***要自信***
要自信

@@@要自信@@@
@@@要自信@@@

请按任意键继续. . .

结论:

string类函数,可以通过其特有的相加功能,将多个类变量组合起来。

 

②返回值是string类变量时,其实质返回的是函数内部创建的变量的副本(原变量在函数结束时已消亡);

 

③返回值是string类的引用变量时,需要注意不要返回函数内部创建的临时变量的引用变量(因为会随着函数结束而消亡,再使用则会出错)。应尽量返回的是参数的引用变量(因为参数不会随着函数结束而消亡);

 

④返回值是引用变量的函数原型之前为什么要加const——虽然之前知道可以避免在返回时同时被修改,也可以直接放在函数参数是const限定的函数调用时的参数之中,但尚不能感受到其优越感(也许以后会知道)

 

⑤函数参数是const限定的好处在于,可以将在调用函数时,将char字符串放在函数的参数位置(这样的话会生成临时变量——因为C-风格字符串和string类不同,但可以被转化为string类);

 

⑥对于在函数中,需要被修改的参数,在函数原型和函数头中,不要加入const进行限定;

 

 

⑦若返回的是参数的引用变量,并将其引用变量赋给另外一个引用变量,那么对另外一个引用变量的修改,将影响参数的值。

 

 

 

继承与引用:

之前提过,读写文件需要依靠ofstream类,而一般输出需要依靠ostream类。

 

ofstream类可以像ostream类那样使用,例如在屏幕上输出字符串,可以这么写:cout<<"abc"<<endl; 而若是写入在文件内(假如文件被mm关联),那么这么写:mm<<"abc<<endl;

 

ofstream类这样,就称为是ostream类的继承。

 

继承:能够使特性从一个类传递给另一个类的语言特性,被称为继承。

 

在以上继承中,ostream类是基类(因为ofstream类是建立在ostream类的基础上的),而ofstream类是派生类(因为是从ostream类上派生而来的)。

 

派生类①继承了基类的方法,这意味着ofstream对象可以使用基类的特性,如precison()setf()——不懂

 

②继承的另一个特征是:基类引用可以指向派生类对象,而无需进行强制类型转换。

这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类作为参数,也可以将派生类作为参数。例如,参数类型为ostream&的函数,可以接受ostream对象(如cout),或者是声明的ofstream对象作为参数。

 

——也就是说,参数除了各种类型,也可以用coutcin之类的作为参数(这样可以根据不同情况,输出到屏幕上或者是写入文件之中)。注意,这种情况需要使用引用,即加上类型标识符&

 

 

如代码:

//读取、写入文件与读取输入、输出到屏幕之上
#include<iostream>
#include<fstream>
#include<cstdlib>	//不知道这个干嘛用的

struct player
{
	char name[20];
	int str;
};

using namespace std;
const int limit = 4;

void file_in(istream&is, char*name, int& value);	//写入
void file_out(ostream&, char[], int);	//读取
void show(const player&);


int main()
{
	ofstream textfile;	//ofstream对象textfile,ofstream是输出/写入到文件
	char filename[10] = "yes.txt";
	textfile.open(filename);
	while (!textfile.is_open())
	{
		cout << "Can't open " << filename << ". Please enter the filename again: ";
		cin >> filename;
		textfile.open(filename);
	}
	player man;
	file_in(cin, man.name, man.str);	//读取用户输入
	show(man);	//显示

	file_out(cout, man.name, man.str);	//输出到屏幕上
	file_out(textfile, man.name, man.str);	//写入文件
	cin.sync();
	cout << "任意按键后清屏。。。" << endl;
	cin.get();
	system("CLS");	//清屏指令
	cout << "按任意键后,从文件之中读取" << endl;
	cin.get();
	textfile.close();	//关闭textfile文件
	ifstream textfile_2;	//ifsstream对象textfile
	textfile_2.open(filename);
	player man_2;	//结构man_2
	file_in(textfile_2, man_2.name, man_2.str);	//读取文件的内容,写入到man_2之中
	show(man_2);	//显示man_2的内容

	system("pause");
	return 0;
}

void file_in(istream& is, char*name, int& value)//读取用户输入,或者读取文件内容
{
	cout << "请输入姓名:";
	is >> name;	
	cout << endl;
	cout << "请输入力量:";
	is >> value;
}

void file_out(ostream& os, char*name, int value)
{
	os << name << endl << value << endl << endl;	//输出到屏幕上,或者输出到文件之中。
}

void show(const player& man)
{
	cout << "姓名:" << man.name << "  力量:" << man.str << endl << endl;
}


解释:

①这段代码的流程:

1)创建ofstream对象,打开一个文件。如果打开失败,那么要求用户输入文件名,直到打开成功为止——貌似不会打开失败,因为默认情况无文件时会自动创建。

2)创建player结构对象man,然后要求用户给结构man的变量赋值(调用file_in函数,第一个参数为cin);

3)显示man的值(调用show函数);

4)将man的值输出到屏幕上(调用file_out函数,第一个参数为cout);

5)将man的值输出到文件之中(调用file_out函数,第一个参数为对象名);

6)清屏;

7)创建ifstream对象,打开之前的文件。

8)从文件之中读取,并将值写入结构man_2之中(调用file_in函数,第一个参数为对象名);

9)显示man_2的值(第一个参数为cout);

 

②当需要使用ostream对象或者istream对象,以及他们的派生类时,函数的原型和定义中,参数需要使用istream&或者是ostream&;

注意:不能用iostream&替代以上两者——虽然我不知道为什么,但编译器提示错误。

 

③当遇见②中的情况时,可以创建一个引用作为cin或者对象名的别名。

这样的话,具体被引用的对象名是什么,根据函数调用时的参数决定。

 

 

 

格式化命令:

这里的格式化命令,指的是让输出的内容有格式,而不是格式化硬盘的那个格式化。

 

命令《一》

cout.width(参数);

效果:设置下一行输出文字的宽度,不足的话,在文字左边用空格补足。参数使用int类型。

 

举个例子,例如cout.width(2);然后cout<<"a"<<endl; 这个时候,输出到屏幕上的是“ a”这样,注意,a左边有一个空格。

注意,只对下一条cout代码(或者其派生类)有效。

 

示例代码:

cout.width(10);

cout << "123456" << endl;

输出为:

    123456

注:1前面有4个空格。

 

 

命令《二》

setf(参数);

效果:方法setf()能够设置各种格式化状态。

 

例如:

①方法调用setf(ios_base::fixed)将对象置于使用定点表示法的模式(显示6位小数)。

然后根据输出的对象的类型,具体情况具体对待。

int类型:不显示小数部分(原因是int是整数)

浮点类型:根据有效数字来显示具体数字,具体取决于浮点类型的精度,但确保显示小数点后六位(例如小数点前数字过多的话,小数点后六位显示为60,原因在于精度不足)。

 

示例:

cout.setf(ios_base::fixed);

cout << 1.0 << endl;

输出为:

1.000000

 

setf(ios::showpoint)将对象置于显示小数点的模式

具体显示几位小数,需要根据设置、以及变量的精度而定。

 

假设变量为a,类型待定,值为12.1,输出为cout<<a<<endl;

情况一:输出的变量为int类型(及类似),输出:12

情况二:输出的变量为float类型,有setf(ios_base::fixed)设置,输出:12.100000

情况三:输出的变量为float类型,无setf(ios_base::fixed)设置,输出:12.1000(即6位有效数字)

情况四:double类型,有(这里及以下指setf(ios_base::fixed)设置),同二。

情况五:double类型,无,同三。

情况六:情况四基础上加cout.precision(1)设置,输出12.1

情况七:double类型,无,加(这里及以下都指cout.precision(1)),输出1.e+01

情况八:double类型,无,加cout.precision(2),输出12.  ←注意这里有点但是没0

 

情况分析:

1)浮点类型,默认显示六位有效数字;

2)整型(int及同类),不显示小数;

3)在setf(ios_base::fixed)的基础上,可以使用cout.precision(参数)来控制显示几位小数后数字。

4)若无setf(ios_base::fixed),那么cout.precision(参数) 其显示的小数位数,为参数-1位。即若参数为1,则只显示小数点,不显示小数后数字。若参数为2,显示小数点后一位数字。

5)显示的数字,受其类型的精度所影响。

6)若有setf(ios_base::fixed),则默认显示小数点后六位(无论类型的精度是否能满足);

7)假如无cout.setf(ios_base::fixed),也无设置小数位数,变量类型为浮点类型,变量无小数部分,那么使用cout.setf(ios::showpoint) 则保证必然显示一位小数(这个时候,小数部分为0)。

 

总结:

1)前提是浮点类型,整型不显示小数点和小数部分;

2)若需要显示小数点,那么使用setf(ios::showpoint)。浮点类型默认显示6位精度。假如浮点变量为整数,且无小数部分,那么小数部分用0补足6位有效数字,若整数部分已满6位,则只显示小数点,而不显示小数点后的0

3)在(2)的基础上,若想控制小数点后显示的位数,使用cout.precision(参数)来进行控制。在无setf(ios_base::fixed)的情况下,这个参数控制有效数字位数。例如1111,然后参数为2,那么会显示为1.1e+03。

4)在(3)的基础上,加入setf(ios_base::fixed)时,为完整版。这个时候,必然能显示小数点位后数字,例如参数为2时,那么必然显示2位小数(且貌似不会使用科学计数法),但小数部分显示的数字受类型精度所影响。

5setf(ios::showpoint)setf(ios_base::fixed)在场的情况下,默认显示六位小数(原因是受后者影响,而非受前者影响,去掉前者结果相同)。

6)也就是说,单独的setf(ios::showpoint)只是让浮点数显示六位有效数字(超过六位则用科学计数法表示浮点数);

7)对于setf(ios::showpoint)来说,precision用来控制有效数字位数。《《《——————存疑

对于setf(ios_base::fixed)来说,precision用于控制小数点位数;

 

也就是说,有setf(ios_base::fixed)在的情况下,有没有setf(ios_base::fixed)没有意义??

另外,ios_base::showpoiont 和 ios::showpoint 是否有区别?对于fixed来说呢?

 

示例:

 

输出:

 

 

命令《三》

precision(参数)

效果:主要用于控制数字位数。

对于setf(ios::showpoint)来说,precision用来控制有效数字位数。《《《——————存疑

对于setf(ios_base::fixed)来说,precision用于控制小数点位数;

 

 

命令《四》

ios_base::fmtflags 变量名;

变量名=cout.setf(ios_base::fixed); //或类似

setf(变量名);

 

效果:①第一行代码用于储存 格式信息(具体包括哪些格式尚不清楚)的,貌似是储存类似setf(ios_base::fixed)之类,有setf(ios_base:: xxxxx)这样的?

②第二行代码用于将其格式赋给这个变量

③第三行代码用于将格式恢复到储存该格式信息(第二行)的时候。

④不能储存cout.precision(1)这样的格式。

 

 

 

 

何时使用引用参数:

①需要修改数据对象。——比使用指针简单

②需要调用数据对象中的一个成员。——例如调用结构中一个成员,和调用整个结构需要的运算时间就是不同的。

 

使用传递的值,而不作修改的函数:

①数据对象很小,如内置数据类型或小型结构,则按值传递

②如果数据对象是数组,则使用指针,因为只有指针能传递数组,并将指针声明为const指针;

③如果数据对象是较大的结构,则使用const指针或者const引用(用指针或引用是避免因创建副本降低效率占用内存,用const是避免数据被修改),以提高程序的效率,可以节省复制结构所需的时间和空间;

④如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按值引用。

 

对于修改调用函数中数据的函数(即参数被修改):

①如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中xint),则很明显,该函数将修改x

②如果数据对象是数组,则只能使用指针。

③如果数据对象是结构,则使用引用或者指针;

④如果数据对象是类对象,则使用引用

 

 

注意:

类对象应使用引用,虽然我不知道为什么。

②数组必须使用指针,因为指针才能传递多个成员(数组,按序排列,指针依次+1);

③结构的原因是,可能有比较大的结构,如果不使用引用或者指针,复制一个副本出来比较占用效率。

④如果不想被修改,用const限定。

 

 

 

 


版权声明:本文首发在云栖社区,遵循云栖社区版权声明:本文内容由互联网用户自发贡献,版权归用户作者所有,云栖社区不为本文内容承担相关法律责任。云栖社区已升级为阿里云开发者社区。如果您发现本文中有涉嫌抄袭的内容,欢迎发送邮件至:developer2020@service.aliyun.com 进行举报,并提供相关证据,一经查实,阿里云开发者社区将协助删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章