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

0302

C++ Primer Plus - 第七章

  • 补充:C++常用的数学函数 --- #include< cmath>
  • 第五、六章
    • 补充:用cin输入int类型时遇到非数字输入时报错,并提示用户重新输入int类型的数据
      • 1.参考链接
      • 2.分析
        • 分析1.遇到非字符输入时cin的状态:
        • 分析2.循环输入时要注意什么?
      • 3.示例代码:
      • 4.升级版:(7.13编程练习 的 `第2题`)
  • 第七章 函数---C++的编程模块
    • 7.0 更新名称空间std的用法
    • 7.1 复习函数的基本知识(以后写函数以`函数原型`的形式来写)
      • 7.1.1 使用函数的三个步骤
      • 7.1.2 函数原型及其作用
    • 7.2 函数参数和按值传递
    • 7.3 函数和数组
      • 7.3.1 示例程序
      • 7.3.2 分析示例程序-1234点
        • 1.int arr[] 和 int* arr
        • 2.形参`arr`只是个指针变量
        • 3.(接收数组名参数的)函数访问的是`原始数组`,而不是其副本
        • 4.用const保护数组
      • 7.3.4 自下而上的程序设计 & 自上而下的程序设计
      • 7.3.5 指针和const (常量指针&指针常量)
        • (1234详细介绍常量指针;5. 指针常量 & 常量指针)
        • 1. 指针-->常规变量:
        • 2. 指针-->指针:
        • 3. 指针-->数组:
        • 4. 结论:当形参是指针时,为保护实参,应尽量将形参声明为`const指针`
        • 5. 常量指针 & 指针常量
      • 7.3.6 STL---begin end
    • 7.4 函数和二维数组
      • 7.4.1 示例代码
      • 7.4.2 分析:
        • 分析1.子函数的形参列表`int (*arr)[4],int num`
        • 分析2.如何访问二维数组的元素:
        • 分析3.没有加`const`?
        • 分析4.参数arr的解释:
        • 分析5.比较一维数组和二维数组:
    • 7.5 函数和C-风格字符串
      • 7.5.1 表示C-风格字符串的方式有3种:
      • 7.5.2 示例代码:
      • 7.5.3 结果:
      • 7.5.4 分析:
    • 7.6 函数和结构体
      • 7.6.1 传递和返回结构体
      • 7.6.2 传递结构体的地址
    • 7.7 函数和string对象
      • string对象数组
    • 7.8 函数与array对象
    • 7.9 递归
    • 7.10 函数指针
      • 7.10.1函数指针的基础知识
        • 1.函数的地址
        • 2.声明函数指针
        • 3.使用函数指针来调用函数
      • 7.10.2 示例
      • 7.10.3 函数指针、函数指针数组、指向函数指针数组的指针
      • 7.10.4 使用typedef进行简化
    • 7.11 总结
    • 7.12 复习题
      • 第9题:字符串常量表示什么意思?---首字符的地址
      • 第9'题:int arr[3]; `arr`和`&arr`的区别是什么?
      • 第10题:按值传递和按地址传递的利弊
      • 第11题:
      • 第12题:结构体和结构体的地址作为参数
      • 第13题:函数指针、函数指针数组、指向函数指针数组的指针 & auto、typedef
    • 7.13 编程练习
      • 第2题:用cin输入int类型,可以提前结束输入,以'q'作为结束输入的标志,如果输入其他非数字的内容,提醒用户重新输入int类型的数据
      • 第7题:使用两个指针来表示数组的区间
      • 第5题:用递归的方式求阶乘
      • 第10题:函数指针(用了typedef)
  • 第八章 函数探幽

补充: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

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
//相当于声明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 使用函数的三个步骤

要使用函数,必须提供定义和原型,并调用该函数
函数定义是实现函数功能的代码;
函数原型描述了函数的接口:传递给函数的(值的数目和种类)以及函数的返回类型;
函数调用使得程序将参数传递给函数,并执行函数的代码。
C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块_第1张图片

7.1.2 函数原型及其作用

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

4.原型的作用
可以帮程序员大大降低程序出错的几率。

5.使用函数原型和不使用函数原型
使用函数原型:
C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块_第5张图片
不使用函数原型:
C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块_第6张图片

7.2 函数参数和按值传递

7.3 函数和数组

7.3.1 示例程序

先看一个例子:

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

7.3.2 分析示例程序-1234点

分析:

1.int arr[] 和 int* arr

子函数sumArr()的参数(形参)int arr[ ]表示的是从主函数传递过来的数组的地址,即arr只是一个指针变量int arr[ ]相当于int* arr,书里面是这么写的
C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块_第7张图片
结论:当且仅当用于函数头或函数原型中,int arr[ ]int* arr的含义是相同的。

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

如果把实参cookies和形参arr的大小打印出来会是怎样的呢?
C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块_第8张图片
结论:
子函数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
C++ Primer Plus(嵌入式公开课)---第7章 函数---C++的编程模块_第9张图片
即:

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是常量,让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 函数和

你可能感兴趣的:(C++,Primer,Plus,c++,1024程序员节)