C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块

0302C++ Primer Plus - 第七章第五、六章第七章 函数---C++的编程模块第五、六章点这里第七章 函数—C++的编程模块
摘要由CSDN通过智能技术生成

0302

C++ Primer Plus - 第七章

补充:C++常用的数学函数 — #include< cmath>

点这里

第五、六章

点这里

补充:用cin输入int类型时遇到非数字输入时报错,并提示用户重新输入int类型的数据

1.参考链接

参考链接1:cin输入错误处理
cin(输入是以回车键结束,遇到空格停止读取);
cin.get( )(遇到回车键结束,遇到空格不结束读取,但是不丢弃输入队列(即缓冲区)中的换行符号);
cin.getline( )(遇到换行结束,遇到空格不结束读取,回车之后,将换行符号丢弃)。

参考链接2:有关cin.sync的用法及解释和如何清除缓冲区
参考链接3:3.cin的条件状态

2.分析

分析1.遇到非字符输入时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)时表示发生非致命错误,则不能继续输入或操作。

分析2.循环输入时要注意什么?

因为是循环输入判断,所以除了修改cin的状态,还要将cin缓存区(输入队列)的内容清空,否则将陷入死循环。有三种方式(第三种只有在vc6.0上可以,在vscode上不行):
cin.ignore(4096,'\n');//清空缓冲区,从输入流cin的缓冲区中提取字符,提取的字符将被忽略,不被使用,直到遇到cin的结束符:回车符’\n’;
while(cin.get() != '\n');//清空缓冲区,逐字符读取缓冲区的内容,直到遇到回车符;
cin.sync();//这个办法只有在vc6.0上可以,在vscode上不行。

3.示例代码:

#include<iostream>

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;
}

4.升级版:(7.13编程练习 的 第2题

用cin输入int类型,可以提前结束输入,以’q’作为结束输入的标志,如果输入其他非数字的内容,提醒用户重新输入int类型的数据。

第七章 函数—C++的编程模块

7.0 更新名称空间std的用法

(见书的2.1.5 名称空间)
对于大型项目来说,用using namespace std;是一个潜在的问题,所以更好的方法是,只使所需的名称可用,这样既省了内存,又不需要在每次要用的时候都必须加std::前缀

#include<iostream>
//相当于声明cout、cin、endl,然后就可以使用了,而不需要在每次要用的时候都加std::前缀
using std::cout;
using std::cin;
using std::endl;

int main(){
   

    cout << "123" << endl;
    int num;
    cin >> num;
    return 0;
}

7.1 复习函数的基本知识(以后写函数以函数原型的形式来写)

7.1.1 使用函数的三个步骤

要使用函数,必须提供定义和原型,并调用该函数
函数定义是实现函数功能的代码;
函数原型描述了函数的接口:传递给函数的(值的数目和种类)以及函数的返回类型;
函数调用使得程序将参数传递给函数,并执行函数的代码。
在这里插入图片描述

7.1.2 函数原型及其作用

1.为什么需要原型?(下图中黄色横线的内容可以看5.补充
在这里插入图片描述
2.原型的语法:
原型最简单的写法就是把函数定义中的函数头复制过来,后面加上分号
函数原型不要求提供变量名,有参数类型列表就足够了,因为在函数原型中定义的名称只在**包含参数列表的括号内**可用,这就是为什么这些名称是什么以及是否出现都不重要的原因第9章 9.2.1 作用域与链接)。
在这里插入图片描述
3.示例
在这里插入图片描述

4.原型的作用
可以帮程序员大大降低程序出错的几率。
在这里插入图片描述
5.使用函数原型和不使用函数原型
使用函数原型:
在这里插入图片描述
不使用函数原型:
在这里插入图片描述

7.2 函数参数和按值传递

7.3 函数和数组

7.3.1 示例程序

先看一个例子:

#include<iostream>

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;
}

7.3.2 分析示例程序-1234点

分析:

1.int arr[] 和 int* arr

子函数sumArr()的参数(形参)int arr[ ]表示的是从主函数传递过来的数组的地址,即arr只是一个指针变量int arr[ ]相当于int* arr,书里面是这么写的
在这里插入图片描述
结论:当且仅当用于函数头或函数原型中,int arr[ ]int* arr的含义是相同的。

2.形参arr只是个指针变量

如果把实参cookies和形参arr的大小打印出来会是怎样的呢?
在这里插入图片描述
结论:
子函数sumArr()的参数(形参)arr是个指针变量
也验证了下面即将要说到的把数组名作为参数时,并不会将整个数组的内容拷贝过来,只会把数组的地址传给子函数。

3.(接收数组名参数的)函数访问的是原始数组,而不是其副本

上面的程序并没有将数组内容传递给函数,而是将数组的位置(地址)、包含的元素种类(类型)以及元素个数提交给函数,这说明:

  1. 传递常规变量时,子函数实用的是(实参)变量的拷贝(即形参),不会影响到main函数中(实参)变量的值;
  2. 但当传递数组时,子函数通过形参(是个指向数组的地址值)找到数组,而并不会把数组的内容拷贝过来,即在子函数中对数组元素进行操作(例如加减操作)时,main函数的数组内容也会发生改变。

结论;
接收数组名参数的函数访问的是原始数组,而不是其副本

那么,将数组的地址作为参数,有什么优缺点呢?
优点:不用将整个数组的内容拷贝一次,会节省时间和内容;
缺点:直接使用原始数据增加了破坏原始数据的风险,解决办法见下面。

4.用const保护数组

先看回上面的例子:

//子函数的声明:
int sumArr(int arr[],int num);//函数原型

//main函数中的进行子函数的调用;
sum = sumArr(cookies,size);//实参cookies

在main函数中对子函数进行调用时,把实参cookiessize分别传递给形参arrnum
如果在子函数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来保护了。

7.3.4 自下而上的程序设计 & 自上而下的程序设计

自下而上的程序设计(bottom-up programming),这种方法非常适合OOP(面向对象编程),它首先强调是数据表示和操纵,即成员变量/属性 + 成员函数/方法
而传统的过程性编程倾向于从上而下的程序设计(top-bottom programming)。

7.3.5 指针和const (常量指针&指针常量)

(1234详细介绍常量指针;5. 指针常量 & 常量指针)
1. 指针–>常规变量:

①将常规变量的地址赋给常规指针

int num = 26;
int* p;
p = &num;
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;
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++;//错误! num不可以变
//(*p)++;//错误! 也不能通过p来修改num的值
int num2 = 10;
//p = &num2;//错误! p也不可以变,即p不能指向别的变量

④将const变量(即常量)的地址赋给常规指针(错误!!!)

const int num = 26;
int* p;
//p = &num; //错误! 因为num是常量,让p指向num,就可以通过p来改变num的值,这样num前面的const状态就很荒谬!!!
2. 指针–>指针:
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指针(错误!!!。

3. 指针–>数组:

这里有个前提:数组元素是基本类型,可以考虑用const来保护原始数组的元素值(即实参),但如果数组元素是指针或者指向指针的指针,就不能用const了。

也和上面类似,①②③合理,④不合理
①非const数组赋给非const指针;
②非const数组赋给const指针;
③const数组赋给const指针;
④const数组赋给非const指针(错误!!!)。

4. 结论:当形参是指针时,为保护实参,应尽量将形参声明为const指针

指针作为函数参数来传递时,可以使用常量指针/const指针来保护数据,即将指针形参声明为指向const的指针。

但如果子函数的功能就是要对实参(例如原始数组的元素)进行改动,那就不需要const来保护了。

如果实参需要保护,就考虑用const指针;
如果实参不需要保护,就不需要const了。

当用const指针指向数组时,有个前提:数组元素是基本类型,可以考虑用const来保护原始数组的元素值(即实参),如果数组元素是指针或者指向指针的指针,就不能用const了。–>7.4 函数和二维数组 中的数组元素就是指针。

5. 常量指针 & 指针常量

之前写过一篇博客:C++笔记4:C++中const和指针

constint* p;的不同位置,就会有不同的效果,下面的①②③;
而常规变量num一直是可以变的,因为它没加const。

①常量指针:p可以变,*p不能变

int num = 26;
const int* p;
p = &num;
num++;//27 num可以变
//(*p)++;//错误! 不能通过p来修改num的值
int num2 = 10;
p = &num2;//p可以指向别的变量 但是p可以变

②指针常量:p不能变,*p可以变

int num = 26;
int* const p;
p = &num;
num++;//27 num可以变
(*p)++;//28 可以通过p来修改num的值
int num2 = 10;
//p = &num2;//错误! p不可以指向别的变量 但是p不可以变

const int* const p;:p不能变,*p也不能变

int num = 26;
const int* const p;
p = &num;
num++;//27 num可以变
//(*p)++;//错误! 不能通过p来修改num的值
int num2 = 10;
//p = &num2;//错误! p不可以指向别的变量 但是p不可以变

7.3.6 STL—begin end

对于处理数组的C++函数,必须将①数组的数据种类、②数组的地址和③数组中元素个数告诉给函数,一般是用两个参数来表示这三个信息:指向数组起始处的指针(①和②)和数组长度(③)。

还可以通过传递两个指针的方式来完成,一个指针指向数组的开头,另一个指针指向数组的结尾。C++标准模板库STL使用“超尾”的概念来指定区间,即第二个指针指向最后一个元素后面的位置
在这里插入图片描述

#include<iostream>

using std::cout; using std::cin; using std::endl;

const int size = 8;
int sumArr(int* begin,int* end)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值