0302
点这里
点这里
参考链接1:cin输入错误处理
cin(输入是以回车键结束,遇到空格停止读取);
cin.get( )(遇到回车键结束,遇到空格不结束读取,但是不丢弃输入队列(即缓冲区)中的换行符号);
cin.getline( )(遇到换行结束,遇到空格不结束读取,回车之后,将换行符号丢弃)。
参考链接2:有关cin.sync的用法及解释和如何清除缓冲区
参考链接3:3.cin的条件状态
通过cin.good()
来查看cin的状态:
cin的goodbit状态(cin.good()
)为1,说明cin当前状态是true;
cin的goodbit状态(cin.good()
)为0,说明cin当前状态是false,就需要cin.clear()
来修改其状态;
或者可以通过cin.rdstate()
来查看cin的状态:
当cin.rdstate()返回0(即ios::goodbit)时表示无错误,可以继续输入或者操作;
当cin.rdstate()返回4(即ios::failbit)时表示发生非致命错误,则不能继续输入或操作。
因为是循环输入判断,所以除了修改cin的状态,还要将cin缓存区(输入队列)的内容清空,否则将陷入死循环。有三种方式(第三种只有在vc6.0上可以,在vscode上不行):
①cin.ignore(4096,'\n');
//清空缓冲区,从输入流cin的缓冲区中提取字符,提取的字符将被忽略,不被使用,直到遇到cin的结束符:回车符’\n’;
②while(cin.get() != '\n');
//清空缓冲区,逐字符读取缓冲区的内容,直到遇到回车符;
③cin.sync();
//这个办法只有在vc6.0上可以,在vscode上不行。
#include
using std::cout; using std::cin; using std::endl;
int main(){
int n=0;
cout << "最开始cin的状态:" << cin.rdstate() << endl;
cout << "请输入一个int型数据:";
cin >> n;
while(!cin)
{
cout << "cin.clear()之前cin的状态:" << cin.rdstate() << endl;
cout<<"goodbit状态:"<<cin.good()<<endl; // 查看goodbit状态,即是否有异常
cin.clear();//修改cin的状态 清除错误标志
cout << "cin.clear()之后cin的状态:" << cin.rdstate() << endl;
cout<<"goodbit状态:"<<cin.good()<<endl; // 清除标志后再查看异常状态
//因为当前是在while循环中,所以要把缓冲区清空后再进行cin输入,否则会陷入死循环
//cin.sync();//这个没办法清空缓冲区
//cin.ignore(4096,'\n');//清空缓冲区,从输入流cin的缓冲区中提取字符,提取的字符将被忽略,不被使用,直到遇到cin的结束符:回车符'\n'
while(cin.get() != '\n');//清空缓冲区,逐字符读取缓冲区的内容,直到遇到回车符
cout << "输入错误!请重新输入一个int型数据:";
cin >> n;
}
cout << "输入结束后cin的状态:" << cin.rdstate() << endl;
cout << "您输入的int类型数据为:" << n << endl;
return 0;
}
第2题
)用cin输入int类型,可以提前结束输入,以’q’作为结束输入的标志,如果输入其他非数字的内容,提醒用户重新输入int类型的数据。
(见书的2.1.5 名称空间)
对于大型项目来说,用using namespace std;
是一个潜在的问题,所以更好的方法是,只使所需的名称可用,这样既省了内存,又不需要在每次要用的时候都必须加std::前缀
。
#include
//相当于声明cout、cin、endl,然后就可以使用了,而不需要在每次要用的时候都加std::前缀
using std::cout;
using std::cin;
using std::endl;
int main(){
cout << "123" << endl;
int num;
cin >> num;
return 0;
}
函数原型
的形式来写)要使用函数,必须提供定义和原型,并调用该函数
函数定义是实现函数功能的代码;
函数原型描述了函数的接口:传递给函数的(值的数目和种类)以及函数的返回类型;
函数调用使得程序将参数传递给函数,并执行函数的代码。
1.为什么需要原型?(下图中黄色横线的内容可以看5.补充)
2.原型的语法:
原型最简单的写法就是把函数定义中的函数头复制过来,后面加上分号。
函数原型不要求提供变量名,有参数类型列表就足够了,因为在函数原型中定义的名称只在**包含参数列表的括号内**可用,这就是为什么这些名称是什么以及是否出现都不重要的原因
(第9章 9.2.1 作用域与链接)。
3.示例
4.原型的作用
可以帮程序员大大降低程序出错的几率。
5.使用函数原型和不使用函数原型
使用函数原型:
不使用函数原型:
先看一个例子:
#include
using std::cout; using std::cin; using std::endl;
const int size = 8;
int sumArr(int arr[],int num);//函数原型
//int sumArr(int* arr,int num);//两种写法是一个意思,在C++中当前仅当用于函数头或者函数原型中时,int* arr和int arr[]的含义才是相同的
int main(void)
{
int cookies[size] = {
1,2,4,8,16,32,64,128};
cout << "实参cookies的大小为: " << sizeof(cookies) << endl;
int sum = 0;
sum = sumArr(cookies,size);//实参cookies
cout << "sum = " << sum << endl;
int n = 26;
int* p = &n;
cout << "*p = " << *p << ";指针变量p的大小为:" << sizeof(p) << "; p = " << p << endl;
return 0;
}
int sumArr(int arr[],int num){
//形参itn arr[]是个指针变量,并不是cookies数组的副本
cout << "形参arr的大小为: " << sizeof(arr) << endl;
int sum = 0;
for(int i = 0;i < num;i++){
sum += arr[i];
}
return sum;
}
分析:
子函数sumArr()
的参数(形参)int arr[ ]
表示的是从主函数传递过来的数组的地址,即arr
只是一个指针变量,int arr[ ]
相当于int* arr
,书里面是这么写的
结论:当且仅当用于函数头或函数原型中,int arr[ ]
和int* arr
的含义是相同的。
arr
只是个指针变量如果把实参cookies
和形参arr
的大小打印出来会是怎样的呢?
结论:
子函数sumArr()
的参数(形参)arr
是个指针变量。
也验证了下面即将要说到的把数组名作为参数时,并不会将整个数组的内容拷贝过来,只会把数组的地址传给子函数。
原始数组
,而不是其副本上面的程序并没有将数组内容传递给函数,而是将数组的位置(地址)、包含的元素种类(类型)以及元素个数提交给函数,这说明:
结论;
接收数组名参数的函数访问的是原始数组,而不是其副本。
那么,将数组的地址作为参数,有什么优缺点呢?
优点:不用将整个数组的内容拷贝一次,会节省时间和内容;
缺点:直接使用原始数据增加了破坏原始数据的风险,解决办法见下面。
先看回上面的例子:
//子函数的声明:
int sumArr(int arr[],int num);//函数原型
//main函数中的进行子函数的调用;
sum = sumArr(cookies,size);//实参cookies
在main函数中对子函数进行调用时,把实参cookies
和size
分别传递给形参arr
和num
,
如果在子函数sumArr()
中对形参num
进行自加一的操作++num;
并不会影响main函数中的实参size
,而如果对形参arr
进行自加一的操作++arr[0]
,main函数中cookies[0]
的值就会从1变成2。
这是由于当C++按值传递数据时,子函数使用的是实参的副本,即把实参拷贝一份赋值给形参;
而接收数组名的子函数将直接面对和使用原始数据,为了防止子函数在无意中修改了数组的内容,可在声明形参是使用关键字const
:
即:
int sumArr(const int arr[],int num);//函数原型
该声明表明:指针arr指向的常量数据(即arr是常量指针,指向常量的指针),const int arr[]
被解释为const double * arr
,这意味着不可以通过arr
来修改cookies[8]数组的元素值,例如++arr[0];
这样的操作是不对的;仅可以使用cookies[8]数组的元素值,例如cout << arr[0];
。
也就是说对于子函数来说,arr所指向的(cookies[8]数组)是只读数据,能使用,不能修改。
但并不是说原始数组cookies[8]必须是常量,而是说对于子函数而言cookies[8]数组的元素值是常量;但在main函数中,原始数组就是个普通数组,即
在main函数中++cookies[0];
是可以的,但在子函数中++arr[0];
是不可以的。
如果子函数的功能就是要对原始数组的元素进行修改,那就不需要const来保护了。
自下而上的程序设计(bottom-up programming),这种方法非常适合OOP(面向对象编程),它首先强调是数据表示和操纵,即成员变量/属性 + 成员函数/方法;
而传统的过程性编程倾向于从上而下的程序设计(top-bottom programming)。
①将常规变量的地址赋给常规指针
int num = 26;
int* p;
p = #
num++;//27 num可以变
(*p)++;//28 *p也可以变,即可以通过p来改变num的值
int num2 = 10;
p = &num2;//p也可以指向别的变量 p也可以变
②将常规变量的地址赋给指向const的指针,即常量指针/const指针
int num = 26;
const int* p;
p = #
num++;//27 num可以变
//(*p)++;//错误! 不能通过p来修改num的值
int num2 = 10;
p = &num2;//p可以指向别的变量 但是p可以变
③将const变量(即常量)的地址赋给const指针
const int num = 26;
const int* p;
p = #
//num++;//错误! num不可以变
//(*p)++;//错误! 也不能通过p来修改num的值
int num2 = 10;
//p = &num2;//错误! p也不可以变,即p不能指向别的变量
④将const变量(即常量)的地址赋给常规指针(错误!!!)
const int num = 26;
int* p;
//p = # //错误! 因为num是常量,让p指向num,就可以通过p来改变num的值,这样num前面的const状态就很荒谬!!!
int num = 26;
int* pd = &age;
const int* pt = pd;//相当于int* pt = &age;
num++;//27 num可以变
(*pd)++;//28 *pd也可以变,即可以通过p来改变num的值
//(*pt)++;//错误! 不能通过pt来修改num的值
常规指针pd和const指针都指向常规变量num,可以通过p来改变num的值,但却不能通过pt来修改num的值。
这是一个将非const指针支付给const指针的例子,这是合理的,类似于上面的②;
但不能将const指针赋给非const指针,类似于上面的④;
即①②③合理,④不合理:
①非const指针赋给非const指针;
②非const指针赋给const指针;
③const指针赋给const指针;
④const指针赋给非const指针(错误!!!。
这里有个前提:数组元素是基本类型,可以考虑用const
来保护原始数组的元素值(即实参),但如果数组元素是指针或者指向指针的指针,就不能用const了。
也和上面类似,①②③合理,④不合理:
①非const数组赋给非const指针;
②非const数组赋给const指针;
③const数组赋给const指针;
④const数组赋给非const指针(错误!!!)。
const指针
将指针作为函数参数来传递时,可以使用常量指针/const指针来保护数据,即将指针形参声明为指向const的指针。
但如果子函数的功能就是要对实参(例如原始数组的元素)进行改动,那就不需要const来保护了。
如果实参需要保护,就考虑用const指针;
如果实参不需要保护,就不需要const了。
当用const指针指向数组时,有个前提:数组元素是基本类型,可以考虑用const
来保护原始数组的元素值(即实参),如果数组元素是指针或者指向指针的指针,就不能用const了。–>7.4 函数和