****本篇大概近万字****
格式:
int a = 1;
int &b = a; //b作为a的别名,是一个a变量的引用,需要在声明时进行初始化
效果:
①b是a的引用变量,b的值和地址,和a是相同的,且不会改变;
②在声明引用变量b时,需要同时对变量b进行初始化,即不能先声明,然后再初始化;原因是引用变量b需要根据他被引用的变量,而拥有地址,而不是有自己独立的内存地址;
③引用变量的地址,由初始化时的变量决定,而不是由最近一次赋值而决定。例如b是a变量的引用,然后又有变量c将值赋给b,那么b的地址依然是a,而不是c;
④引用变量其在声明的时候,变量名前的“&”是类型标识符的一部分,而不是地址运算符。
在之后在引用变量名前加上“&”,才是地址运算符;
⑤引用变量的类型,需要和被引用的变量的类型相同。即a是int类型,那么引用变量b也应该是int类型;
⑥引用变量和指针不同。
指针是指向某个内存地址,而引用变量是变量的别名。
指针有其储存自己的内存地址(即指针指向的地址,是储存指针的内存地址的值),而引用变量的地址,就是被引用的变量的内存地址。
⑦可以将引用变量理解为变量的另外一个名字,例如上面代码中的a和b,在很多地方的使用方式是相同的。只不过引用变量还有自己独特的使用范围。
如代码:
#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 请按任意键继续. . .
总结:
①从上面的代码中可以看出,b和a的地址、值是相同的;
②在中间b=c,然而b并没有和c的地址保持相同,而是和a的地址依然保持相同,但b的值、以及a的值,和c的值相同,相当于将c的值,赋给了a和b;
③当引用变量或者被引用的变量的值改变时,另外一个值也一样被改变(因为实质上是一个值有两个变量名);
④可以一个变量有多个引用变量。如在之前代码的基础上,加上int &d=a或int &d=b; 然后d的地址和值,也将和a、b保持相同;
⑤必须声明引用变量的时候进行初始化。
引用变量作为函数参数时:
引用变量作为函数参数时,可以像指针那样直接修改变量的值,但是具体方式不同。
使用指针修改实参时,传递的实参应为变量的地址,在函数内部,应该如同使用指针那样,通过地址进行操控值;
例如代码:
//使用指针修改变量的值 #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 进行限定)。
但右值引用在函数中,似乎也不会像引用变量那样更改变量的值。
将引用用于结构:
与基本类型(如int、double等)相比,引用更适合用在结构和类上。
和基本类型的用法相同,希望修改传递的结构变量,则不使用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对象作为参数。
——也就是说,参数除了各种类型,也可以用cout、cin之类的作为参数(这样可以根据不同情况,输出到屏幕上或者是写入文件之中)。注意,这种情况需要使用引用,即加上类型标识符&;
如代码:
//读取、写入文件与读取输入、输出到屏幕之上 #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是整数)
浮点类型:根据有效数字来显示具体数字,具体取决于浮点类型的精度,但确保显示小数点后六位(例如小数点前数字过多的话,小数点后六位显示为6个0,原因在于精度不足)。
示例:
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位小数(且貌似不会使用科学计数法),但小数部分显示的数字受类型精度所影响。
(5)setf(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)这样的代码(其中x是int),则很明显,该函数将修改x。
②如果数据对象是数组,则只能使用指针。
③如果数据对象是结构,则使用引用或者指针;
④如果数据对象是类对象,则使用引用。
注意:
①类对象应使用引用,虽然我不知道为什么。
②数组必须使用指针,因为指针才能传递多个成员(数组,按序排列,指针依次+1);
③结构的原因是,可能有比较大的结构,如果不使用引用或者指针,复制一个副本出来比较占用效率。
④如果不想被修改,用const限定。