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

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来保护原始数组的元素值(即实参),但如果数组元素是指针或者指向指针的指针&#

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: "C Primer Plus" 是一本经典的 C 语言教程书籍,它旨在为初学者提供一种深入浅出、易于理解的方式来学习 C 语言的基础知识和应用技巧。这本书结构清晰,内容详实,深入浅出地讲解了 C 语言的各个方面,包括变量、数据类型、运算符、流程控制、函数、指针、结构体、文件操作等。通过逐步增加难度的方式,读者可以逐步领会 C 语言的精髓,从初学者成长为具备一定经验和技能的程序员。 与其他 C 语言教程不同的是,"C Primer Plus" 强调实战教学,让读者通过不断练习和编写实际程序来理解和掌握 C 语言的各种应用,从而在实际工作中更加得心应手。此外,书中也提供了大量的练习题和实例代码,方便读者自行探索和实践。 总之,"C Primer Plus" 是一本教授 C 语言的经典著作,它基于深入的理论知识,提供了大量的实战编程练习机会,适合想要深入学习 C 语言的初学者和有一定经验的程序员。 ### 回答2: 《C Primer》中文版是一本经典的C语言教程,原版为《C Primer Plus》,是一本聚焦于C语言程序设计入门、进阶和实践的杰出著作。本书全面覆盖了C语言语法、函数、指针、数组、字符串、文件操作、内存管理等核心内容,包含丰富的示例和练习,让学习者逐步了解和掌握C语言的核心要素。 《C Primer》中文版相比于英文原版,更方便中国学生阅读和理解。该书的内容体系完整,难度逐渐递增,因此对于C语言零基础的读者来说,也能循序渐进地学习C语言。该书知识点分类清晰,讲解详细,涵盖了C语言绝大部分特性和工具,有助于读者构建系统的学习框架,能够从小白逐步走向合格C程序员的道路上。 《C Primer》中文版是学习C语言必备的参考书籍之一,它不仅适用于学生自学,也适用于教授C程序设计的教师作为教材使用。它具有深入浅出、通俗易懂和良好的示范性质。对于只有一定编程经验的读者,该书也是适合挑战的好书。无论你是初学者还是进阶爱好者,它都能帮助你打下坚实的编程基础,快速提升编程实践能力,成为一个有成就的程序员。 ### 回答3: C Primer Plus 中文版是一本通俗易懂、系统完整的C语言教材,由Stephen Prata编写。其最新版为第六版,内容涵盖了从C语言的基础概念到高级特性的知识点,详细介绍了函数、指针、数组、结构体、文件操作、动态内存分配等重要概念和技术应用。全书分为26,每都配有大量的例子和练习题,使读者能够深入了解C语言的应用和开发方法,同时提供了大量的修改和调试技巧。 C Primer Plus 中文版不仅适用于初学C语言的新手,也可以作为已有C语言基础但需要提高的人员的参考书。此外,该书还涵盖了C语言编程的规范要求和工程实践,非常适合从事C语言编程的软件开发人员阅读和学习。总之,C Primer Plus 中文版是学习C语言的入门必备教材之一,无论是基础学习和提高深入,都具有重要的学习价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值