CPP-L2备课记录

Preface

本节将继续讲解上一节CPP-L1课程中,可能给孩子带来困惑的地方,比如:

  1. 操作系统是做什么的?有哪些主要类型的操作系统?这里需要大概普及一下操作系统的作用和Windows/Unix(like)这么两类系统。又由于接下来要讲到变量的作用域和生命周期、库和函数的调用,不得不对操作系统的环境变量知识进行简单的科普,只讲到修改和使用即可;
  2. 变量的作用域和生命周期,内存的申请和释放;
  3. 函数(库)的声明和函数的调用,包括本地函数,系统库,第三方库,声明和使用顺序。函数的调用原理涉及到系统内核的CPU时间片分配、堆栈、程序计数器、进程、线程等复杂知识的内容不好讲,只能简单带过,让孩子们大概知道怎么回事。又由于函数有参数,此处又涉及到形参和实参的问题(值传递和引用传递),此处尚且涉及指针,所以不能不讲,但也只能简化的讲;
  4. 上一节使用的是C语言进行讲解,讲清楚了库,就可以讲解C++了。然后将printf修改为cout,再带孩子实践一下cout和cin,加深一下函数调用的印象。

0. 预备知识-计算机基础

0.1. 小管家:操作系统

0.1.1. 由来

说法一:计算机由控制系统,运算系统,输入系统(Input),输出系统(Output),存储系统五部分组成;

说法二:计算机由控制系统,运算系统,输入输出(IO)系统,存储系统,数据通路(总线)五部分组成;

        再具体一些,就是在物理上,使用不同的材料,实现的逻辑门电路的集群,构成了“计算机”,计算机按照约定的顺序和位置,不停的执行人类的指令。为了让人类能够灵活的,自由的使用计算机内部的各种器件,人类编写了一个庞大的软件,称为“操作系统”,它负责协调组织计算机的所有“硬件”。

        形象化一些,操作系统是你的小管家。你家里有很多家电、书籍、衣物、食品等,都由你的小管家帮你管理。当你需要什么东西的时候,只需要找你的小管家即可,无需自己去翻箱倒柜的寻找。

0.1.2. 分类

在这里插入图片描述
当前主流操作系统,按照其名称,主流的有:

  • Windows(微软:家用)
  • MacOS(苹果:设计/编程,Unix)
  • Linux(Linus:服务器,Unix Like)
  • BSD(Berkeley Software Distribution,服务器,如FreeBSD、NetBSD和OpenBSD,Unix Like)
  • IOS(苹果:Unix)
  • Android (谷歌:Unix Like,Linux再套一层UI)

按照API(Application Programming Interface)规范分类有:

  • POSIX:MacOS Linux BSD IOS Android
  • 其它:Windows

下面是一张Unix系统的版本树进化图可供参考:
Unix Evolution

0.1.3. “约定”的意义

        POSIX:可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX )是IEEE(电气电子工程师学会)为要在各种UNIX操作系统上运行的软件而定义的一系列API标准。分为2个部分:

  • 第一部分是针对API,第二部分是针对用户交互和工具,其国际标准名称是IEEE Std 1003.9-1992(https://ieeexplore.ieee.org/document/182910)
  • 第二部分是针对用户交互和工具,其国际标准名称是ISO/IEC 9945-2:19939 ANSI/IEEE Std 1003.2-1993(https://ieeexplore.ieee.org/document/8584040)。

        简单来说,POSIX就像是有一位法官,规定了世界上所有的手机充电器,必须是长成什么样子,这样子任何一个工厂只需要按照这个规定来生产充电器和手机,那么全世界的充电器都可以给世界上任意一台手机充电了。

在这里插入图片描述
【参考文献】:
[1]. Dennis M. Ritchie. The UNIX system: The evolution of the UNIX time-sharing system. Nokia Bell Labs, AT&T Bell Laboratories Technical Journal, October 1984.
[2]. History and Timeline. The Open Group.
[3]. 一口Linux. posix是什么都不知道,还好意思说你懂Linux?. 知乎, 2021-07-23.
[3]. 阮一峰. Unix版权史. ruanyifeng.com, 2010年3月18日.

0.2. 快递员和他的工作:变量和函数

0.2.1. 计算的步骤

  • 我们先来看计算机计算乘法的过程,和小朋友做作业是否一样呢?

计算的过程:👉找数字 👉算算术 👉写答案

0.2.2. 作用域和声明周期

现在,我们用快递员一天的工作来举例,讲讲变量的作用域和生命周期:

  1. 快递员像是变量,每天送不同的东西(赋值可以变化)
  2. 快递员上班(变量声明)
  3. 快递员每天的工作场所就是取货点和驿站(工作区域 → 大括号作用域,起作用的区域)
  4. 下班 → 出了作用域,他就没法送货了(变量生命周期结束)
  • 例0-2-2-1:快递员的一天
    让我们实践一下,尝试编译下面的代码,看会发生什么,为什么?
int main(int argc, char** argv){
	// E1 start
	bool is_ready = true;  // 快递员开始上班了 
    if (is_ready) { // 判断:快递员上班了吗?
	    int courier = 10; // 快递员手里有10个快递 
	    int deliver_count = 1; // 每次送达1个 
    	  printf("送货前:快递员手里还有%d件快递\n", courier);
    	  courier = courier- deliver_count;
    	  printf("送货后:快递员手里还有%d件快递\n", courier);
    }
    printf("下班了:快递员手里还有%d件快递\n", courier);
	// E1 end
}

在这里插入图片描述
最后,我们猜一下,我们使用完的内存,在超出了作用域以后,里面还有东西吗?

内存本身不会被主动清零
手动分配的(堆)内存(new/malloc):必须显式释放(delete/free)
全局/静态变量:生命周期到程序结束。
函数内的普通变量(如 int, float, 对象等)分配在栈上,后续的栈操作(如函数调用)可能覆盖该内存。

备注:这里的内容按照孩子们目前的知识量是无法理解的,这里的特别注意是写给老师注意的,老师需要自己组织语言告诉孩子内存没释放是很危险的,释放内存要从娃娃抓起。

0.3. 你的衣服谁生产的:什么是函数(库)

  • 例0-3-1:你在商店购买一件红色布料,黑色纽扣的男童衬衣,这背后发生了什么?

我们来看:

  1. 服装厂向塑料制品加工厂订购了一批黑色纽扣;
  2. 服装厂向纺织厂订购了一批红色布料;
  3. 服装厂使用黑色纽扣和红色布料生产了一批男童衬衣;
  4. 商店向服装厂购买了100件红色布料黑色纽扣的男童衬衣;
  5. 你在商店买到了衣服。

请添加图片描述

在这整个流程(流转的过程)中,服装厂不知道塑料制品加工厂、纺织厂如何生产的各种产品,但是它知道只要告诉它们所需的颜色、尺寸,他们就能提供对应的产品!

  • 例0-3-2:让我们用编程知识来分析这其中的关键点吧:
  1. 塑料制品加工厂、纺织厂、服装厂就像函数(方法)库,里面有各种各样的函数,比如裁剪、缝纫、装箱、下单、送货。
  2. 有些函数可以提供给外部使用,比如下单。还有些函数只能自己内部使用,比如裁剪、缝纫。
  3. 一些函数需要参数,来告诉它客人需要什么东西,比如塑料制品加工厂提供的函数就规定了,必须告诉它需要什么东西(纽扣),以及颜色(黑色)和数量。

观察物体至少考虑5点要素:颜色、大小(体积)、方向、位置、数量
思考事件至少考虑5点要素:时间(When)、地点(Where)、人物(Who)、事件(What)、原因(Why)

至此,整个买衣服的流程就可以整理成为更加清晰的流程:

  1. 服装厂通过钮扣厂提供的下单函数(函数名),向塑料制品加工厂订购了1000(参数1)个黑色(参数2)的纽扣(参数3);
  2. 服装厂通过纺织厂提供的下单函数(函数名)订购了500(参数1)米红色(参数2)布料;
  3. 服装厂将步骤2订购的红色布料(步骤2函数调用的返回值),送到了自己工厂的生产车间,让裁剪部门负责裁剪(函数名)成衣服毛坯(返回值类型),又把裁剪出的衣服毛坯(裁剪函数返回值)和步骤1订购的黑色纽扣(步骤1函数调用返回值)送到缝纫部门进行缝纫(函数名)并要求生产200件衬衣(指定返回值),最后缝纫部门一共生产出了200件红色布料黑色纽扣的男童衬衣
  4. 商店通过服装厂的下单函数(函数名),订购了10(参数1)件红色布料黑色纽扣的男童衬衣(参数2),过了几天,收到了货物(服装厂下单函数的返回值);
  5. 你在商店买到了衣服。

请添加图片描述

0.4. 小管家帮你拿衣服:函数声明、系统环境变量

别人是如何让我能调用他写的函数的?我能不能也让别人可以调用我的函数?
当然可以,让我们学习函数的声明、头文件(header)和源码文件之间的关系吧!

  • 例0-4-1:有一天,你需要洗衣服,于是你拿过来一个“服务菜单”,上面记录了你的小管家能帮你做的所有事情。你发现服务列表里,有存取物品和洗衣服服务,那这件事不就可以直接让小管家去做了吗?你的小管家(操作系统)帮你洗衣服(运行程序),整个过程可以整理成这样的:
  1. 你查看**服务菜单(小管家头文件)**确认小管家会不会洗衣服;
  2. 你给小管家写指令(编写源代码),里面用到了小管家可以提供的服务(引用头文件+调用外部函数);
  3. 小管家拿到你写的指令本(程序源代码),发现需要用到的东西是衣服洗衣机预编译指令引用了洗衣机的说明书),于是通过**“家庭物品位置索引”记事本(环境变量),查到洗衣机在阳台,衣服在衣柜,洗衣机说明书在书架(系统函数库)**里;
  4. 小管家找到了洗衣机,然后按照它的使用说明(系统函数库里洗衣机的头文件),去阳台(索引指向的第三方依赖 or 要链接的源码的位置),把洗衣机需要洗的脏衣服(参数)丢进去开始洗(系统调用 or 链接源码)
  5. 存取衣服的操作也是同样的道理。

在这里插入图片描述
由这个例子我们可以看出,函数声明和环境变量最主要的作用可以比喻成去饭店吃饭:

  1. 你是客人,要在餐馆点餐,先要看菜单里有没有(头文件有没有定义)
  2. 你是客人,你点不同的菜(函数),就要付不同的钱(参数),餐馆就会给你不同的结果(功能实现)
  3. 你是厨师,就要告诉客人你会做什么菜(函数定义),需要多少钱(参数定义),你做的菜的照片(返回值定义)
  4. 你是厨师,做饭的时候需要用到的工具和蔬菜(第三方依赖),需要查阅餐馆的仓库清单(环境变量 or 系统函数库)

Unix(like)系统中定义环境变量:

系统变量:/etc/profile
用户变量:~/.bashrc

Windows中定义环境变量

系统变量:Windows徽标键 -> 设置 -> 系统 -> 高级系统设置 -> 环境变量 -> 系统变量
用户变量:Windows徽标键 -> 设置 -> 系统 -> 高级系统设置 -> 环境变量 -> %UserName%的用户变量

使用预编译指令告诉编译器需要使用别人提供的功能

#include 头文件名.h

备注:尝试自己编写一个头文件,并在main中引用自己编写的服务

0.5. 参数:形参和实参的区别

  • 我们先来看一个简单的例子:发苹果
#include <iostream>
#include <string>
using namespace std;

int fa_pingguo_1(int a){ // 值传递
	printf("fa_pingguo_1:我的名字叫小a,我家地址为:%d号,我家里有%d个糖果\n", &a, a);
	a = a + 1;
	printf("fa_pingguo_1:我是住在%d的小a,现在我家里有%d个糖果啦!\n", &a, a);
	// 总结:可以理解为,main里面的小a有几个糖果,就发给fa_pingguo_1里的小a几个糖果 
}

// 引用传递,收到的是“家庭住址”,加*号来“住址”变量和普通变量
int fa_pingguo_2(int* a){ 
	// 操作地址变量时不带*号可以直接看地内存址是多少,带了则看里面存的东西 
	printf("fa_pingguo_2:我的名字叫小a,我家地址为:%d号,我家里有%d个糖果\n", a, *a);
	*a = *a + 1;
	printf("fa_pingguo_2:我是住在%d的小a,现在我家里有%d个糖果啦!\n", a, *a);
	// 总结:可以理解为,直接按照main里面的小a家的地址,去发苹果 
}

int main(int argc, char** argv){
	int a = 5; // 实参 
	printf("main        :我的名字叫小a,我家地址为:%d号,", &a);//&号可以取到变量的内存地址
	printf("我家里有%d个糖果\n\n", a);//不用&号,取到的是地址里面放的东西
	
	// 发糖果 
	printf("main        :第一次开始发糖果(值传递)\n");
	fa_pingguo_1(a); // 传递实参的值
	printf("main        :我是住在%d的小a,第一次发糖果后,我家里有%d个糖果\n\n", &a, a);
	
	// 发糖果 
	printf("main        :第二次开始发糖果(引用传递)\n");
	fa_pingguo_2(&a); // 传递内存地址
	printf("main        :我是住在%d的小a,第二次发糖果后,我家里有%d个糖果\n\n", &a, a);
	
	/*
		总结:
		普通变量看内容:直接用变量名
		普通变量看地址:前面加&
		地址变量看内容:前面加*
		地址变量看地址:直接用变量名 
	*/
	
	// 暂停 
	cin.get(); 
	cin.get(); 
}

运行结果如下:
在这里插入图片描述

总结:
1. 普通变量看内容:直接用变量名
2. 普通变量看地址:前面加&
3. 地址变量看内容:前面加*
4. 地址变量看地址:直接用变量名

普通要值直接喊,
想看地址 & (安)去办!
指针要值加 * (星)号,
指针地址原名算!

  • 还可以用一个复杂的例子来讲解:
    在这里插入图片描述
#include <iostream>
#include <string>
using namespace std;

// 洗衣函数(指针版)
void wash_with_pointer(string* name, string* state, int temp) {
    *state = "干净";
    *name += "(指针版已洗)";
}

// 洗衣函数(值传递版)
void wash_by_value(string name, string state, int temp) {
    state = "干净";
    name += "(值传递版已洗)";
}

int main() {
    cout << "=== C++洗衣方式对比演示 ===" << endl;
    
    // 准备两组相同的衣物
    string pointer_coat = "红色外套", pointer_state = "脏";
    string value_coat = "红色外套", value_state = "脏";

    // 洗衣前状态
    cout << "\n洗衣前状态:";
    cout << "\n[指针组] " << pointer_coat << ":" << pointer_state;
    cout << "\n[值传递组] " << value_coat << ":" << value_state;

    // 开始洗衣(相同水温40℃)
    wash_with_pointer(&pointer_coat, &pointer_state, 40);
    wash_by_value(value_coat, value_state, 40);

    // 洗衣后状态
    cout << "\n\n洗衣后状态对比:";
    cout << "\n[指针组] " << pointer_coat << ":" << pointer_state;
    cout << "\n[值传递组] " << value_coat << ":" << value_state;

    // 原理说明
    cout << "\n\n原理分析:";
    cout << "\n指针传递:函数内修改直接影响原变量";
    cout << "\n值传递:函数内修改的是副本,原变量不变";
    cout << "\n提示:可以观察两组外套名称的变化差异\n";

    return 0;
}

1. 实践:修改程序使用C++函数库

1.1. 尝试修改库文件引用

试试<stdio.h>和<iostream.h>调用printf,会发现一样的。

1.2. 修改printf为cout

给孩子讲解namespace(命名空间)。初衷是为了避免重复的函数命名,比如塑料制品厂和纺织厂都有下单函数,服装厂的采购人员采购纽扣和布料,很明显不能用同一张单据,怎么办?于是塑料制品厂的采购单前面增加一个台头,叫:塑料制品厂采购单,而纺织厂的采购单叫做布料采购单

<stdio.h>里没有cout,<iostream.h>有cout。iostream是stdio“增强版”。

1.3. 实现一个你去商店买衣服的函数

  • 在Dev-C++ 5.11版本中编译器选择TDM-GCC 4.9.2尝试不加namespace而使用cout和cin试试会发生什么?为什么?初识软件的版本管理。

  • 尝试把buy函数写在main函数下面试试会发生什么?为什么?理解程序的顺序执行结构。

#include <iostream>
#include <string>

using namespace std;

int buy(string color, string product, int count){
	cout << "请查收" << count << "件" << color << "的" << product << "\n";
}

int main(int argc, char** argv){
	string product;
	string color;
	int count;
	cout << "请告诉我您要买什么:";
	cin >> product;
	cout << "请告诉我您什么颜色的:";
	cin >> color;
	cout << "请告诉我您几件:";
	cin >> count;
	buy(color, product, count);
}

编程时,最常遇到的问题有2种:(依赖的)版本不兼容和路径(门牌号)错误
解决不兼容问题有一句名言:在计算机领域内的问题,没有什么是多抽象一层解决不了

在这里插入图片描述

2. Conclution:

  1. 操作系统屏蔽了硬件层的复杂管理,所有硬件生产厂商需要面向操作系统定义的标准,提供硬件的使用方法。所有要使用硬件的用户,需要按照操作系统定义的使用规范来调用硬件。所以,操作系统就是一个抽出来的“中间层”
  1. 变量有作用域,是在{和}内,超出以后会出错
  1. 变量有生命周期,写代码要注意哪些类型需要手动释放内存
  1. 想用别人提供的功能,要用#include指令告诉计算机,你要用谁提供的功能
  1. 值传递和引用传递即使现在用不着,也要背熟:
    普通要值直接喊,
    想看地址 & (安)去办!
    指针要值加 * (星)号,
    指针地址原名算!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值