华清远见嵌入式培训_第二周回顾与反思

目录

前言

周一

一、switch..case

1.1 注意事项

1.2 使用练习

二、循环控制语句

2.1 使用goto实现循环

2.2 while循环

2.3 do..while 循环

2.4 for 循环

2.5 死循环

2.6 辅助控制关键字

周二

一、数组

1.1 一维数组

1.2 数组越界问题

1.3 二维数组

1.4 编码练习

二、冒泡排序

2.1 原理动图

2.2 代码实现(以降序为例)

周三

一、字符数组和字符串

1.1 概述

1.2 几个注意事项

二、字符串处理函数

2.1 strlen

2.2 strcpy

2.3 strcat

2.4 strcmp

2.5 编码练习

 三、指针

3.1 概念

3.2 指针相关的操作

3.3 指针和变量和关系

3.4 指针的基本使用

3.5 指针的大小

3.6 指针的运算

周四

一、指针

1.1 大小端存储问题

1.2 指针和一维数组

1.3 指针和二维数组

1.4 数组指针

1.5 指针数组

1.6 指针和字符串

1.7 二级指针

周五

 一、const关键字

二、函数

1.1 函数的概念

1.2 函数的定义和调用

2.3 函数的声明

2.4 函数的参数

2.5 函数的返回值

2.6 全局变量和局部变量

2.7 函数的传参方式

2.8 数组的传参方式

2.9 二维数组的传参方式

2.10 main函数的参数

周末作业

反思与总结


前言

        这是培训的第二个周末,闭眼细想,明显感觉学的东西比上周要多,这周里结束了C语言基础这一部分,补充学习了控制语句、新学了一维数组、二维数组、字符数组、字符串、指针和函数,重点难点是后面的指针和函数,也是老师花了最多篇幅讲解的,因此篇文章也将会,对指针函数做着重复习和回顾。

        同样,写此文章,是想以这种碎碎念的方式回顾重点、重复盲点、加深印象,复习、总结和反思本周的学习,仅供后期自己回顾使用。知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!

周一

一、switch..case

1.1 注意事项

1.switch后面()里的表达式: 变量或者完整表达式。

        一般是是整型或者字符的(不能是浮点型的)

2.case后面的表达式: switch后面()中表达式可能的结果。

3.break的作用: 执行完某个分支的代码块就立即结束整个switch..case语句。

如果不加 break,程序会继续向下执行下面case的代码块,直到遇到break或者整个switch..case语句结束,这种现象叫做---case击穿”

4.default 分支相当于 if..else 语句中的else部分,如果前面的case都不满足,则执行default分支,如果不关心其他分支的情况,整个default分支都可以不要

1.2 使用练习

编码实现一个简易的计算器功能( + - * 功能即可):

        要求在终端输入表达式,输出计算的结果

        如:输入:5+6 则输出 11

#include 
int main(){    
    int lvalue = 0;
    char operator = 0;
    int rvalue = 0;
    scanf("%d%c%d", &lvalue, &operator, &rvalue);
    switch(operator){
        case '+':
            printf("%d + %d = %d\n", lvalue, rvalue, lvalue + rvalue);
            break;
        case '-':
            printf("%d - %d = %d\n", lvalue, rvalue, lvalue - rvalue);
            break;
        case '*':
            printf("%d * %d = %d\n", lvalue, rvalue, lvalue * rvalue);
            break;
        default:
            printf("目前只支持 +  -  * 运算\n");
            break;
    }
    return 0;
}

二、循环控制语句

2.1 使用goto实现循环

2.1.1 注意事项

goto本身是用来实现代码跳转的。

注意:只能在同一个函数中实现跳转。

注意:goto的跳转对代码的逻辑性和易读性有一定的影响,所以要谨慎使用。

2.1.2 循环实现

使用 goto 实现 计算 1+2+3+....+100 的和。

#include 

int main(int argc, const char *argv[])
{
	int sum = 0;
	int i = 1;//既用来控制循环结束 又用来控制每次加的值
	//一般用来控制循环结束的变量 称之为 循环变量  一般使用 i  j  k 等表示

LOOP:
	sum = sum + i;
	i++;
	if(i <= 100){
		goto LOOP;
	}

	printf("sum = %d\n", sum);//5050

	return 0;
}

2.2 while循环

        优先使用while循环的情况:循环次数不确定情况。

2.2.1 格式

while(表达式){
    循环体;//就是要循环执行的代码块
}

2.2.2 代码练习

猴子吃桃问题:

        猴子第一天摘了若干个桃,当即就吃了一半数量的桃,没吃过瘾,又多吃了一个;第二天又将剩下的桃,吃了一半,没吃过瘾,又多吃了一个;以后的每天,依次类推,都吃掉一半数量再多一个。直到第10天再想吃桃的时候,发现只剩下一个了。

        问:猴子第一天摘了多少个桃。使用while循环实现。

思路:第十天剩1个,第9天剩(1+1)*2个,以此类推,借助循环,day>1是循环条件。

#include 
int main(){
    int count = 1;
    int i = 0;
    while(i < 9){
        count = (count+1) * 2;
        i++;
    }
    printf("第1天有 %d 个桃\n", count);//1534
    return 0;
}

2.3 do..while 循环

        优先使用do..while循环的情况:在循环开始之前,需要先执行一次循环内的代码。

2.3.1 格式

        注意:while后要加一个分号!

do{
    代码块;
}while(表达式);  

2.3.2 while和do ..while的区别

while :         先判断 后执行

do_while:        先执行 后判断

不管表达式为真还是为假,do_while里面的代码块 至少要执行一次。

2.4 for 循环

        优先使用for循环的情况:循环区间明确的情况。

2.4.1 格式

这三个表达式,如果哪个不需要,可以不写,但是两个引号“ ;; ”必须要写。

for(表达式1;表达式2;表达式3){
    循环体;
}

2.4.2 代码练习

1、输出 [100,999] 范围内所有的水仙花数:

        水仙花:个*个*个 + 十*十*十 + 百*百*百 == 自身

        如:153 就是一个水仙花数

        153 == 1*1*1 + 5*5*5 + 3*3*3 = 1+125+27 == 153

#include 

int main(int argc, const char *argv[])
{
	int num = 0;
	int g = 0;
	int s = 0;
	int b = 0;

	for(num = 100; num <= 999; num++){

		g = num % 10;
		s = num / 10 % 10;
		b = num / 100;

		if(num == g*g*g + s*s*s + b*b*b){
			printf("%d 是水仙花数\n", num);
		}
	}

	return 0;
}

2、输出 [2,100] 范围内所有的质数

#include 

int main(){
    int num = 0;
        
    int i = 0;
    for(num = 2; num <= 100; num++){
        for(i = 2; i < num; i++){
            if(num % i == 0){
                break;
            }
        }
        if(i == num){
            printf("%d 是质数\n", num);
        }
    }
    return 0;
}

2.5 死循环

//所谓的死循环就是程序一直在循环中执行循环体,无限循环。

while(1){        //常用的写法
//循环体
}

for(;;){         //注意,表达式可以不写,但是两个分号必须要写,否则报错
//循环体
}

2.6 辅助控制关键字

2.6.1 break

       1、break可用在switch..case 语句中,表示执行完某个分支就立即结束整个switch..case语句。

       2、break也可以用在循环中,表示结束本层循环

2.6.2 continue

        continue只能用在循环中,表示结束本层的本次循环

2.6.3 return

       1、 return用在函数中,是用来返回函数执行的结果的。

       2、 如果在主函数中使用 return,表示立即结束整个程序

周二

一、数组

        数组是构造类型,是用来存储一组相同的数据类型的数据的。

        数组的元素在内存上的都是连续的,不管几维数组,都是连续的。

1.1 一维数组

        格式:        存储类型  数据类型  数组名[下标]

        例如:           默认为auto      int      s[10]                              

       一维数组: 下标只有一个数的数组

        注意:

                在定义数组时,下标必须是常量,表示告诉操作应该给这个数组分配多大的内存空间;

                在其他场景下,既可以是常量,也可以是变量,也可以是表达式,表示访问数组中的第几个元素。

1.2 数组越界问题

#include 

#define N 5

int main(int argc, const char *argv[])
{
	int s[N];
	
	//使用数组时一定要注意检查边界
	//数组越界的错误编译器不会检查 需要程序员自己检查
	//数组越界的错误是不可预知的
	//可能不报错
	//可能段错误
	//也可能修改了不该修改的数据
	s[234789] = 100;
	printf("s[234789] = %d\n", s[234789]);

	return 0;
}

1.3 二维数组

        下标有两个的数组:s[3][4],第一个下标表示行数,第二个表示列数。

1.4 编码练习

使用二维数组保存杨辉三角,并输出。

华清远见嵌入式培训_第二周回顾与反思_第1张图片

#include 

int main(){
    int s[10][10] = {0};
    
    int i = 0;
    int j = 0;
    //控制行
    for(i = 0; i < 10; i++){
        s[i][0] = 1;
        //控制列
        for(j = 1; j <= i; j++){
            s[i][j] = s[i-1][j-1] + s[i-1][j];
        }
    }
    //输出
    for(i = 0 ; i < 10; i++){
        for(j = 0; j <= i; j++){
            printf("%4d", s[i][j]);
        }
        putchar(10);
    }
    
    return 0;
}

二、冒泡排序

2.1 原理动图

华清远见嵌入式培训_第二周回顾与反思_第2张图片

2.2 代码实现(以降序为例)

#include 
int main(){
    int s[10] = {12, 34, 56, 5, 14, 98, 60, 68, 70, 17};
    int len = sizeof(s)/sizeof(s[0]);

    int i = 0;
    int j = 0;
    int temp = 0;
    for(i = 0; i < 10; i++){
        printf("%d  ", s[i]);
    }
    printf("\n");

    for(j = 0; j < len-1; j++){

        for(i = 0; i < len-1-j; i++){
            if(s[i] < s[i+1]){
                temp = s[i];
                s[i] = s[i+1];
                s[i+1] = temp;
            }
        }
    }
    i = 0;
    for(i = 0; i < 10; i++){
        printf("%d  ", s[i]);
    }
    printf("\n");
    return 0;
}

周三

一、字符数组和字符串

1.1 概述

        字符数组:数组中的每个元素都是一个char类型的变量;

char s1[5] = {'h','e','l','l','o'};

        字符串:连续的字符,在字符数组中以‘\0’结尾。

char s3[6] = "12345"; //比较常用的写法

1.2 几个注意事项

        1、char s[]="hqyj";        sizeof(s4) 结果是5 因为结尾的 '\0' 也会被计算;

        2、C语言中字符串的操作全是找 '\0' 做为结束条件;

        3、0 '\0' '0' 前面三个零中,前两个是一样的 ascii码都是 0,第三个ascii码是48;

        4、不完全初始化,没有初始化的位用0来初始化,0就是 '\0'。

二、字符串处理函数

2.1 strlen

        int a = strlen(s[5]);        计算字符串的长度,注意:此长度不包括“\0”.

2.2 strcpy

        strcpy(s1, s2);              将s2中的字符串拷贝到s1中,以s2的‘\0’结尾。

                                            注意:保证s1的空间要比s2的大。

2.3 strcat

        strcat(s1, s2);                将s2中的字符串追加到s1后,从s1的‘\0’开始,到s2的‘\0’结束。

                                             注意:保证s1的空间要比(s1+s2)大。    

2.4 strcmp

        int a = strcmp(s1, s2);        逐个比较两个字符串中对应字符的ascii码

                                                   直到出现大小关系就立即返回只有两个字符串中

                                                   第一次出现 \0 之前的所有字符都相等 才认为是相等

        注意:strcmp的返回值为>0,<0,==0,具体值为第一个不相同位置的ascii码差值(s1-s2);

2.5 编码练习

在终端输入一个字符串,将其翻转,并输出。

#include 

int main(){
	char str[32] = {0};
	gets(str);
	printf("str = [%s]\n", str);
	
	int i = 0;
	int j = 0;
	char temp = 0;
	while(str[i] != '\0'){
		i++;
	}
	//当循环结束时  i 是 '\0' 的下标
	i--;
	
	//交换
	while(j < i){
		temp = str[j];
		str[j] = str[i];
		str[i] = temp;
		j++;
		i--;
	}
	
	printf("str = [%s]\n", str);
	
	return 0;
}

 三、指针

        虽然之前上学的时候学过一遍,但这知识它不进脑子啊,不过按照老师这次讲的:家与快递员原理,算是更清楚明白了一点:

3.1 概念

        内存中每个字节都有一个编号,这个编号就是指针,也叫作地址。
        专门用来存储这个编号的变量,叫做指针变量。

        通常情况下,地址指的是地址编号;指针指的是指针变量。

3.2 指针相关的操作

        &    :取地址符,获取变量的地址。
                  对于多字节的变量,取地址取到的是首地址,标号最小的那个。
        *    :定义指针变量的时候,*只起到标识作用,标识定义的是一个指针变量,
其他场景下,表示操作指针指向的空间里的内容

3.3 指针和变量和关系

        

华清远见嵌入式培训_第二周回顾与反思_第3张图片

         理解与解释:int a 就是我的家,我的家里放着一个快递(10);int *p = &a,此时是指针的定义,*起的作用是标识定义的是一个指针变量,实际上是快递员p在通讯录里存下的我家的地址,不要和*p混了,*p是指快递员p到了我家,拿到了我要寄的快递(10)。

        注意:还有几个场景*只起到标识:函数的结构定义里,int *p 也是指定义了一个一级指针,准备接受主函数要传的参数,int **p 是指定义了一个二级指针准备接收其他函数要传的一级指针的地址。

        后面这个指针的值传递和地址传递还会细说,只是看到这个地方突然明白了,上课没明白的的一个地方。引上课上的这个练习:

#include 

int m = 10;
int n = 20;

//指针的值传递
void my_chage1(int *x){
	x = &n;
}

//指针的地址传递
void my_chage2(int **x){
	*x = &n;
}

int main(int argc, const char *argv[])
{
#if 0
	//指针的值传递
	int *p = &m;
	my_chage1(p);
	printf("*p = %d\n", *p);//10
#endif
    //指针的地址传递
	int *p = &m;
	my_chage2(&p);
	printf("*p = %d\n", *p);//20
	printf("p = %p,  &n = %p\n", p, &n);//一样的

	return 0;
}

3.4 指针的基本使用

        int a = 10;int *p = &a;*p = 20;

        在上面的这几个定义中,只要明白a、&a、p和*p的关系了,后面的使用就比较好理解了。课上讲的这些例子,涵盖了需要注意的大多数情况:

#include 

int main(int argc, const char *argv[])
{
	//在定义变量的时候,操作系统会根据变量的类型 给变量分配内存空间
	int a = 10;

	//通过变量名可以操作对应的空间
	a = 20;

	//通过& 可以获取变量的地址
	//使用  %p  输出地址
	printf("&a = %p\n", &a);

	//指针变量可以用来保存地址编号
	//定义指针的格式
	//数据类型 *指针变量名;
	int *p1 = &a;
	printf("p1 = %p\n", p1);

	//指针p保存了变量a的地址 称为 指针p指向变量a
	//指针指向变量之后 通过指针 也可以操作变量对应的空间
	*p1 = 1314;
	printf("a = %d  *p1 = %d\n", a, *p1);//1314 1314

	//不要使用普通变量来保存地址
	//long value = &a;//保存可以保存 有警告
	//printf("value = %#lx\n", value);
	//*value = 520;//但是不能对普通变量取 * 操作
	
	//指针只能保存已经分配了的地址
	//int *p2 = 0x12345678;//这种用法容易造成内存非法访问
	//printf("p2 = %p\n", p2);
	//*p2 = 520;
	
	//指针类型的作用:决定了从指针保存的地址开始,一共能操作多少个字节
	//int *类型的指针 能操作 4个字节
	//char *类型的指针 只能操作 1个字节
	
	//一行中可以定义多个指针,但是要注意
	//int *p3,p4;//这种写法 p3是指针  p4是普通的int变量
	int *p3,*p4;//这是正确的写法

	//定义指针时如果没有初始化,里面存的也是随机值
	//这种指针叫做 野指针 --野指针是有害的 错误不可预知
	//int *p5;
	//*p5 = 1234;
	//printf("*p5 = %d\n", *p5);
	
	//定义指针时如果不知道用谁初始化 可以先使用 NULL 来初始化
	//这种指针叫做 空指针
	int *p6 = NULL; //NULL 本质是  (void *)0
	//对NULL 取*操作一定是段错误
	//*p6 = 1314;

	return 0;
}

3.5 指针的大小

        32位系统:指针都是4字节的;
        64位系统:指针都是8字节的。

3.6 指针的运算

        指针能做的运算:
                        算数运算:+  -    ++  --
                        关系运算:>  <    ==  !=
                        赋值运算: =

        指针里面存的都是地址编号,所以,指针的运算,就是地址的运算。既然是地址运算,能做的运算就是有限的了。相同类型的指针之间做运算才有意义。

        课上老师也讲了几个指针运算过程中需要注意的地方:

        1、指针加法,加的是多少个这个指针数据类型的大小;

        2、指针减法,得到的是相差的数据类型的个数;++、--同理。

        其他情况参考下面的总结:

#include 

int main(int argc, const char *argv[])
{
	int s[5] = {10,20,30,40,50};
	//指针加上一个整数n 表示 :加上n个指针的数据类型的大小
	int *p1 = &s[0];
	int *p2 = p1+1;
	printf("*p1 = %d\n", *p1);//10
	printf("*p2 = %d\n", *p2);//20
	printf("p1 = %p , p2 = %p\n", p1, p2);//相差一个int的大小

	//指针的强传是安全的 因为指针的大小是一样的
	char *p3 = (char *)&s[0];
	char *p4 = p3+1;
	printf("p3 = %p , p4 = %p\n", p3, p4);//相差一个char的大小

	//指针的减法
	//两个指针变量做差 得到的结果 是相差的数据类型的个数
	//而不是相差多少个字节!!!!
	int *p5 = &s[0];
	int *p6 = &s[3];
	int ret = p6 - p5;
	printf("p5 = %p , p6 = %p\n", p5, p6);//相差3个int的大小
	printf("ret = %d\n", ret);//3

	//要注意下面的用法
	//int s[5] = {10,20,30,40,50};
	int *p7 = &s[0];
	int v1 = *++p7;
	printf("v1 = %d   p7 = %p  &s[1] = %p\n", v1, p7, &s[1]);//20 后两个地址一样
	int *p8 = &s[0];
	int v2 = *p8++;
	printf("v2 = %d   p8 = %p  &s[1] = %p\n", v2, p8, &s[1]);//10 后两个地址一样
	int *p9 = &s[0];
	
	int v3 = (*p9)++;//这种写法 就是  int v3 = s[0]++;
	printf("v3 = %d, s[0] = %d\n", v3, s[0]);//10  11

	//指针的关系运算
	if(p6 > p5){
		printf("yes\n");
	}else{
		printf("no\n");
	}

	//指针的赋值运算
	//指针变量本质也是一个变量 变量允许相互赋值
	int a = 10;
	int b = 20;
	int *pp1 = &a;
	int *pp2 = &b;
	pp2 = pp1;

	return 0;
}

思考:下面的代码会输出什么
int *p = NULL;
printf("%d  %d  %d\n", p+1, p, (p+1)-p);

答案:4  0  1

分析:定义一个int类型的指针p指向空NULL;

        指针的加法运算,p+n,对于此题加的是n个int的类型数据的大小,即加了4*n,所以p+1,即:0+1*4 = 4;

        空指针的值为0;

        指针的减法运算,得到的值为两个指针数据类型的个数,(p+1)和 p 相差一个数据类型,所以相减值为1;

周四

        今天一整天都在学习指针,一级指针、二级指针、指针数组、数组指针,催眠利器,但没睡,学的很认真!

一、指针

1.1 大小端存储问题

        不同的CPU和操作系统对多字节数据的存储方式可能不一样。

        分为小端存储和大端存储。

华清远见嵌入式培训_第二周回顾与反思_第4张图片

笔试题:请写一个简单的C语言程序,来判断你使用的主机是大端存储还是小端存储。

#include 

int main(int argc, const char *argv[])
{
	int num = 0x12345678;
	char *p = (char *)#
	if(0x12 == *p){
		printf("大端\n");
	}else if(0x78 == *p){
		printf("小端\n");
	}
	return 0;
}

思考题:小端存储的主机上,下面的代码会输出什么

        int num = 0x41424344;

        printf("%s\n", &num);

答案:DCBA+不确定的值;

分析:华清远见嵌入式培训_第二周回顾与反思_第5张图片

1.2 指针和一维数组

        对照老师在程序中举得例子,一目了然:

#include 

int main(int argc, const char *argv[])
{
	int s[5] = {10, 20, 30, 40, 50};

	//数组名就是数组的首地址
	printf("s = %p\n", s);
	printf("s = %p\n", s+1);//相差一个int
	
	//研究一维数组 数组名[下标] 方式访问成员的本质
	printf("*s = %d\n", *s);//10
	printf("*(s+1) = %d\n", *(s+1));//20
	//也就是说  数组名[下标] 方式访问成员的本质是
	//s[i] <==> *(s+i)
	
	//可以定义一个指针指向一维数组
	//int *p = &s[0];//正确的 不常用
	int *p = s; //数组名就是首地址  最常用的写法
	//int *p = &s;//&s[0]  s  &s  三个值是一样的
				//这种写法相当于给地址升维了 基本不使用
				//记住 永远不要对数组名取地址
	//printf("%p  %p  %p\n", &s[0], s, &s);
	
	//指针指向数组后 通过指针也可以操作数组的元素了
	printf("*(p+3) = %d\n", *(p+3));//40
	printf("p[4] = %d\n", p[4]);//50

	//指针指向数组后 有下面的等价关系
	//s[i] <==> *(s+i) <==> p[i] <==> *(p+i)
	
	//指针p和数组名s的区别:
	//p是指针 是变量 可以被赋值 也可以++
	//s是数组名 是地址常量 不可以被赋值 也不可以++
	
	//一维数组的遍历
	int i = 0;
	for(i = 0; i < 5; i++){
		//printf("%d  ", s[i]);
		//printf("%d  ", *(s+i));
		//printf("%d  ", p[i]);
		printf("%d  ", *(p+i));
	}
	putchar(10);

	return 0;
}

思考题:32位 小端存储的主机下, 下面的代码会输出什么

        int s[5] = {1, 2, 3, 4, 5};

        int *p = (int *)((int)s+1);

        printf("%x\n", *p);

答案:2000000

分析:

1.3 指针和二维数组

        1、二维数组的数组名是一个行指针,操作空间是一整行

        2、对二维数组的数组名取 * 操作,相当于给指针降维

        3、将操作空间是一整行的行指针,降维成操作空间是一个元素的列指针

        4、对列指针再取 * 操作,才是操作数据

        5、有下面的等价关系:

                        s[i] <==> *(s+i)

                        s[i][j] <==> *(*(s+i)+j) <==> *(s[i]+j)

#include 

int main(int argc, const char *argv[])
{
	int s[3][4] = {{1,2,3,4},
					{5,6,7,8},
					{9,10,11,12}};
	//研究二维数组数组名的操作空间
	printf("s = %p\n", s);
	printf("s+1 = %p\n", s+1);//相差4个int  也就是一行元素的大小
	//所以说 二维数组的数组名 是一个 行指针 操作空间是一整行
	
	//对二维数组的数组名 取 * 操作,相当于给指针降维
	//将操作空间是一整行的行指针 降维成 操作空间是一个元素的 列指针
	printf("*s = %p\n", *s);
	printf("*s+1 = %p\n", *s+1);//相差1个int

	//对列指针再取 * 操作 才是操作数据
	printf("**s = %d\n", **s);//1
	printf("*(*(s+0)+0) = %d\n", *(*(s+0)+0));//1
	printf("*(*(s+0)+2) = %d\n", *(*(s+0)+2));//3
	printf("*(*(s+2)+2) = %d\n", *(*(s+2)+2));//11

	//也就是说 有下面的等价关系
	//s[i] <==> *(s+i)
	//s[i][j] <==> *(*(s+i)+j) <==> *(s[i]+j)
	
	//注意:二维数组的数组名是一个行指针 操作空间是一整行
	//已经超过了基本类型的操作范围了
	//所以不能使用普通的指针来指向二维数组
	//因为普通的指针 操作空间 是有限的
	//int *p = s;//一般不这样使用
	//需要使用数组指针来指向二维数组
	
	//二维数组的遍历
	int i = 0;
	int j = 0;
	for(i = 0; i < 3; i++){
		for(j = 0; j < 4; j++){
			//printf("%d  ", s[i][j]);
			//printf("%d  ", *(s[i]+j));//这种写法不常用
			printf("%d  ", *(*(s+i)+j));
		}
		putchar(10);
	}

	return 0;
}

1.4 数组指针

        本质是一个指针,指向一个二维数组。也叫作行指针

        数组指针一般多用于函数中 将二维数组作为参数传递时。

        我的理解就是:还是那个快递员,只不过我家地址是个楼房,楼上也有住户,指针指向一层楼。

        格式:        数据类型 (*指针名)[列宽];

        例如:        int (*p)[4] = s;

        注意与指针数组区分:        数据类型 *数组名[长度];

                                                    char *p[4] = {NULL};

        数组指针有如下等价关系:

                                                s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==>

                                                p[i][j] <==> *(p[i]+j) <==> *(*(p+i)+j)

        数组s和指针p的区别:        数组s是常量;指针p是变量。

#include 

int main(int argc, const char *argv[])
{
	int s[3][4] = {{1,2,3,4},
					{5,6,7,8},
					{9,10,11,12}};
	//定义了一个数组指针p 指向二维数组s
	int (*p)[4] = s;

	//数组指针指向二维数组后 操作和使用数组名的操作是一样的
	//也就是说 有如下的等价关系
	//s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==>
	//p[i][j] <==> *(p[i]+j) <==> *(*(p+i)+j)
	
	//二维数组的遍历
	int i = 0;
	int j = 0;
	for(i = 0; i < 3; i++){
		for(j = 0; j < 4; j++){
			//printf("%d  ", s[i][j]);
			//printf("%d  ", *(s[i]+j));//这种写法不常用
			//printf("%d  ", *(*(s+i)+j));
			//printf("%d  ", p[i][j]);
			//printf("%d  ", *(p[i]+j));//这种写法不常用
			printf("%d  ", *(*(p+i)+j));
		}
		putchar(10);
	}

	//s和p的区别还是
	//s是常量
	//p是变量

	return 0;
}

这里老师讲了一个问题:

        之所以不能对一维数组数组名取地址的原因:

#include 

int main(int argc, const char *argv[])
{
	int s[5] = {1,2,3,4,5};
	
	//对数组名s  取&操作 相当于给指针升维
	//本来操作空间是一个元素 升维成了 一行元素
	//而我们的p是一个 int *类型的指针 操作空间就只有一个int
	//所以类型不匹配 会报警告
	//int *p = &s;
	//printf("%d\n", p[2]);//3

	//可以使用数组指针来消除这种警告
	int (*p)[5] = &s;
	//但是此时的p 基本上已经没有意义了
	//因为此时的p +1 就加了一整行了 而一维数组 只有一行
	//也就是说 p+1 就已经越界了
	//所以不要不要使用  对一维数组数组名取地址的写法

	return 0;
}

1.5 指针数组

        本质是一个数组,数组中每个元素都是一个指针。

        可以把他,理解成快递公司,里面全是快递员。

        格式:         数据类型 *数组名[长度];

        例如:        char *name2[4] = {NULL};        //定义了一个指针数组,数组名叫 name2

                                                                           //数组中有4个元素

                                                                           //每个元素都是一个char * 类型的指针

课上讲的例子

#include 

int main(int argc, const char *argv[])
{
	//处理多个字符串时 可以将其保存在二维数组中
	char name[4][64] = {
			"zhangsan",
			"lisi",
			"fulajimier.fulajimiluoweiqi.pujing",
			"xiaohong"};
	printf("%s\n", name[0]);
	printf("%s\n", name[1]);
	printf("%s\n", name[2]);
	printf("%s\n", name[3]);
	//但是这种做法会造成空间上的严重浪费,因为需要以最长的字符串为准
	
	printf("----------------------------------\n");
	
	//也可以使用指针数组来处理
	//定义了一个指针数组  数组名叫 name2 数组中有4个元素
	//每个元素都是一个char * 类型的指针
	char *name2[4] = {NULL};

	//因为数组中每个元素都是一个char *指针
	//所以取出数组的元素后 操作就和 操作char * 指针是一样的
	name2[0] = "zhangsan";
	name2[1] = "lisi";
	name2[2] = "fulajimier.fulajimiluoweiqi.pujing";
	name2[3] = "xiaoming";

	printf("%s\n", name2[0]);
	printf("%s\n", name2[1]);
	printf("%s\n", name2[2]);
	printf("%s\n", name2[3]);

	return 0;
}

        处理多个字符串的时候,用指针数组可以有效节省空间,指针数组里面的指针分别指向对应的字符串。

1.6 指针和字符串

        首先讲了虚拟内存,虚拟内存的空间划分:

华清远见嵌入式培训_第二周回顾与反思_第6张图片

        注意几点:

        1、字符数组定义在 栈区,里面存的字符串常量在 字符串常量区;可以通过数组操作它所存储的字符串。

        2、指针定义在 栈区,指针指向的字符串在 字符串常量区;无法直接修改 字符串常量区的内容。

#include 

int main(int argc, const char *argv[])
{
	//可以将字符串保存在字符数组中
	//s1 数数组 在栈区   "hello world" 是字符串常量 在字符串常量区
	//这个操作相当于用字符串常量区的 "hello world"
	//初始化栈区的数组s1
	char s1[] = "hello world";
	//对s1的操作 操作的是栈区的
	//栈区是允许修改的
	s1[0] = 'H';
	printf("s1 = %s\n", s1);//Hello world

	//栈区 即使多个数组保存同一个字符串 多个数组的地址 也不一样
	char s2[] = "hello world";
	printf("s1 = %p, s2 = %p\n", s1, s2);//不一样的


	//也可以定义一个指针 直接指向字符串
	//这种写法 指针变量p1 在栈区 但是指向的地址是字符串常量区的地址
	char *p1 = "hello world";
	printf("p1 = %s\n", p1);//hell world
	//字符串常量区的内容是不允许修改的!!!!!!!!!!!!
	//p1[0] = 'H';//错误的操作
	
	//多个指针只要指向同一个字符串常量 那么保存的地址 就是一样的
	char *p2 = "hello world";
	printf("p1 = %p, p2 = %p\n", p1, p2);
	
	return 0;
}

1.7 二级指针

        二级指针是用来保存一级指针的地址的。

        二级指针多用于 将一级指针的地址作为函数的参数传递时

        知道这几点就够了,例子在一级指针里分析过了,注意这里的  *  是起到的标识作用

华清远见嵌入式培训_第二周回顾与反思_第7张图片

周五

        今天是课前讲题幸运儿,讲了前一天老师留的课堂作业:

编码:实现 atoi 函数的功能。--字符串转
        char str[32] = {0};
        scanf("%s", str);//1234
        //你的操作
        printf("%d");//1234
思路:循环 * 10 + 下一位数字

#include 

int main(int argc, const char *argv[])
{
	char str[32] = {0};
	scanf("%s",str);
	printf("%%s = %s\n",str);//从终端输入
	
	int num = 0;
	char *p = str;
	while(*p){				//遍历转换
		num = num*10 + (*p-'0');
		p++;
	}
	printf("%%d = %d\n",num);

	return 0;
}

实现过程详解:

(循环打印对应变量的值,生成以下过程)

%s = 4399


当指针指到第 0 位时,指针指向字符 '4';        之前的num = 0;
字符 4 的ASCII码值为:52;        字符 0 的ASCII码值为: 48;
(*p - '0 ') = 52 - 48 = 4;        4 就是这个字符型数字 '4' 的整形数值;
此时num = num * 10 + (*p-'0') = 4;

当指针指到第 1 位时,指针指向字符 '3';        之前的num = 4;
字符 3 的ASCII码值为:51;        字符 0 的ASCII码值为: 48;
(*p - '0 ') = 51 - 48 = 3;         3 就是这个字符型数字 '3' 的整形数值;
此时num = num * 10 + (*p-'0') = 43

当指针指到第 2 位时,指针指向字符 '9';        之前的num = 43;
字符 9 的ASCII码值为:57;        字符 0 的ASCII码值为: 48;

(*p - '0 ') = 57 - 48 = 9;         9 就是这个字符型数字 '9' 的整形数值;
此时num = num * 10 + (*p-'0') = 439

当指针指到第 3 位时,指针指向字符 '9';        之前的num = 439;
字符 9 的ASCII码值为:57;        字符 0 的ASCII码值为: 48;
(*p - '0 ') = 57 - 48 = 9;         9 就是这个字符型数字 '9' 的整形数值;
此时num = num * 10 + (*p-'0') = 4399

%d = 4399

一、const关键字

        const 修饰变量时,表示不能通过变量名,来修改变量的值。

        保险锁,防止运行过程中变量的数据被改变。

	const int a = 10;
	//a = 20;//错误的  const修饰的变量不允许通过变量名修改
	printf("%d\n", a);

        const 修饰指针的时候:(可能会出笔试题)

const int *p;
int const *p;
int * const p;
const int * const p;
//区分时要看 const  和 * 的相对位置关系
//const 在 * 的左边,表示修饰的是 *p
    //表示不能通过指针p修改指向的空间里的内容 指针的指向是可以修改的
//const 在 * 的右边,表示修饰的是 p
    //表示允许通过指针p修改指向的空间里的内容 指针的指向是不可以修改的
//如果*的左右都有const 表示都不能修改了 

        比如:

#include 

int main(int argc, const char *argv[])
{
#if 0
	int a = 100;
	int b = 200;
	const int *p = &a;
	//*p = 150;//错误的 不允许通过指针修改指向空间里的内容
	a = 150;//通过变量名 还是可以修改的
	p = &b;//正确的 指针的指向允许修改
#endif

#if 0
	int a = 100;
	int b = 200;
	int const *p = &a;//和上面的写法是一样的 只不过不常用
#endif

#if 0
	int a = 100;
	int b = 200;
	int * const p = &a;
	*p = 150;//正确的 允许通过指针修改指向空间的内容
	//p = &b;//错误的  指针的指向不允许修改
#endif

	int a = 100;
	int b = 200;
	const int * const p = &a;
	//*p = 150;
	//p = &b;
	//上面两行都是错误的 都不允许修改

	return 0;
}

二、函数

1.1 函数的概念

        打包成块,使用时,随时调用;如:atoi strlen putchar 等。

1.2 函数的定义和调用

        定义函数的格式:

        注意:函数名也是一个标识符,要符合标识符的命名规范。

返回值类型 函数名(函数的参数列表){
    函数体;//也就是我们要实现功能的代码块
}

2.3 函数的声明

        在程序的开头对封装的函数进行标识,防止因为顺序问题而报错。

        格式是在文件的开头,返回值类型 函数名();        注意括号内的参数也要写全。

2.4 函数的参数

        作用:函数的调用者将这些值通过参数的形式给函数传递进去。

        形参:用来告诉调用者,使用这个函数需要几个,什么类型的参数,在函数调用的过程中 操作系统会给形参分配空间用来保存实参的值。函数调用结束时 操作系统会回收形参占用的空间

        实参:用来表示本地调用用哪些数据给函数传参,实参的个数和类型要和形参保持一致,在调用的过程中相当于用 实参去初始化形参。

        例如:

#include 

//有参数的函数声明
void my_add(int x, int y);
//void my_add(int, int);//这样写也可以

//定义函数时,()里面的叫做函数的形式参数,简称形参
	//用来告诉调用者,使用这个函数需要 几个 什么类型的参数
//在函数调用的过程中 操作系统会给形参分配空间 用来保存实参的值
//实参和形参不在同一块内存空间!!!!!!
//函数调用结束时 操作系统会回收形参占用的空间

//功能:计算两个整数的和
void my_add(int x, int y){
	int temp = x+y;
	printf("ret = %d\n", temp);
}

int main(int argc, const char *argv[])
{
	my_add(10, 20);//有参数的函数调用

	int a = 100;
	int b = 200;
	//函数调用时,()里面的叫做 实际参数 简称实参
		//用来表示本地调用用哪些数据给函数传参
	//实参的个数和类型要和形参保持一致
	//在调用的过程中相当于用 实参去初始化形参
	my_add(a, b);//有参数的函数调用

	return 0;
}

2.5 函数的返回值

        有些时候需要将函数执行的结果,返回给调用处,供后面使用;

        如果需要返回值就写,如果不需要,也可以不写。

        返回值对于实际开发过程中,常用于判断函数的执行情况。

2.6 全局变量和局部变量

        全局变量:        没有被任何{} 扩住,全局变量不初始化 里面也是0;

                生命周期:        整个程序结束;

                作用域:        整个文件都可以访问;

        局部变量:        被{}包住的都叫做局部变量;

                作用域和生命周期:        最近的{}里面;

2.7 函数的传参方式

2.7.1 全局传参

        在函数之外定义并初始化变量,进行传参,作用域和生命周期都是整个程序,一般不使用。

2.7.2 复制传参(值传递)

        将实参的值拷贝一份,赋值给形参。

        注意:形参不管如何改变,都不会影响到实参,因为形参是实参不在同一块内存空间。

2.7.3 地址传参(地址传递)

        以指针定义形参,此时,传给形参的是实参的地址,以此可以通过形参修改实参的内容。

        比较难理解,结合下面代码和练习,综合理解。

#include 

void my_add(int x, int y, int *z){
	printf("my_add: z = %p\n", z);//和实参 ret的地址一样
	*z = x+y;
	printf("my_add: *z = %d\n", *z);//30
}

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = 20;
	int ret = 0;
	printf("main:&ret = %p\n", &ret);
	my_add(a, b, &ret);//a和b是值传递  此处的&ret 地址传递
	printf("main:ret = %d\n", ret);//30

	return 0;
}

封装一个能实现两个整数交换的函数,my_swap(),调用并测试。

#include 

int my_swap(int *x, int *y){
	if(NULL == x || NULL == y){
		return -1;
	}
    int temp = *x;
    *x = *y;
    *y = temp;
	return 0;
}

int main(){
    int a = 10;
    int b = 20;
    my_swap(&a, &b);
    printf("a = %d, b = %d\n", a, b);
    
    return 0;
}

形参是指针,也不一定是地址传递,也可能是指针的值传递。

这一块,在函数里面定义指针时,* 起的是标识的作用。

#include 

int m = 10;
int n = 20;

//指针的值传递
void my_chage1(int *x){
	x = &n;
}

//指针的地址传递
void my_chage2(int **x){
	*x = &n;
}

int main(int argc, const char *argv[])
{
#if 0
	//指针的值传递
	int *p = &m;
	my_chage1(p);
	printf("*p = %d\n", *p);//10
#endif
    //指针的地址传递
	int *p = &m;
	my_chage2(&p);
	printf("*p = %d\n", *p);//20
	printf("p = %p,  &n = %p\n", p, &n);//一样的

	return 0;
}

2.8 数组的传参方式

2.8.1 字符串的传参方式

        字符串的传参只需要传递首地址,以字符串有 '\0' 结束。

#include 

char *my_strcpy(char *dest, const char *src){
	char *temp = dest;//备份目标字符串的首地址 用作返回值
	while(*src != '\0'){
		*dest++ = *src++;
	}
	*dest = *src;
	return temp;
}

int main(int argc, const char *argv[])
{
	char s1[32] = "hello";
	char s2[32] = "abcd";

	my_strcpy(s1, s2);

	printf("s1 = %s\n", s1);
	printf("s2 = %s\n", s2);

	return 0;
}

2.8.2 整数型数组的传参方式

        整型数组传参时:既要传递数组的首地址,也要传递数组的长度,因为整型数组是没有结束标志的。

#include 

//遍历一维数组的函数

//数组传参的写法:写指针即可----常用的写法
void print_array1(int *p, int n){
	int i = 0;
	for(i = 0; i < n; i++){
		printf("%d  ", p[i]);
	}
	printf("\n");
}

//数组传参使用下面的写法也可以
//这两种写法叫做 代码的自注释 是给程序员看的
//这两中写法中 p 也是指针  不是数组
//void print_array2(int p[], int n){
void print_array2(int p[100], int n){
	printf("sizeof(p) = %ld\n", sizeof(p));//8 说明p是指针
	int i = 0;
	for(i = 0; i < n; i++){
		printf("%d  ", p[i]);
	}
	printf("\n");
}

int main(int argc, const char *argv[])
{
	int s1[5] = {10, 20, 30, 40, 50};
	print_array1(s1, 5);

	int s2[10] = {1,2,3,4,5,6,7,8,9,10};
	print_array1(s2, 10);

	print_array2(s1, 5);
	print_array2(s2, 10);

	return 0;
}

2.9 二维数组的传参方式

        二维数组传参时,需要用数组指针作为形参

#include 

//遍历二维数组的函数
void print_array(int (*p)[4], int h, int l){
	int i = 0;
	int j = 0;
	for(i = 0; i < h; i++){
		for(j = 0; j < l; j++){
			printf("%d  ", p[i][j]);
		}
		printf("\n");
	}
}

int main(int argc, const char *argv[])
{
	int s[3][4] = {{1,2,3,4},
					{5,6,7,8},
					{9,10,11,12}};
	print_array(s, 3, 4);

	return 0;
}

2.10 main函数的参数

        这块虽说是听明白了,但是没太理解是用来干啥的,作业能写出来,感觉似懂非懂。

华清远见嵌入式培训_第二周回顾与反思_第8张图片

#include 

int main(int argc, const char *argv[])
{
	//argc是命令行执行命令时 参数的个数 (包括可执行文件)
	printf("argc = %d\n", argc);

	int i = 0;
	for(i = 0; i < argc; i++){
		printf("%s\n", argv[i]);
	}
	
	printf("argv[0] = %s\n", argv[0]);//可执行文件名

	return 0;
}

周末作业

作业1:把冒泡排序的代码封装成函数,要求多设置一个参数 flag,用户调用函数时 给flag传0,表示升序排序,传1表示降序排序:

代码实现:

#include 

void my_paixu(int *p,int m,int n);

void my_paixu(int *p,int m,int n){
	int i = 0;
	int j = 0;
	int temp = 0;
	if(n == 1){
		for(j = 0; j < m-1; j++){
        	for(i = 0; i < m-1-j; i++){
            	if(p[i] > p[i+1]){
               		temp = p[i];
                	p[i] = p[i+1];
                	p[i+1] = temp;
            	}
        	}
    	}
	}
	if(n == 0){
		for(j = 0; j < m-1; j++){
        	for(i = 0; i < m-1-j; i++){
            	if(p[i] < p[i+1]){
               		temp = p[i];
                	p[i] = p[i+1];
                	p[i+1] = temp;
            	}
        	}
    	}
		
	}
	i = 0;
    for(i = 0; i < m; i++){
        printf("%d  ", p[i]);
    }
    printf("\n");
}

int main(int argc, const char *argv[])
{
	int s[5] = {0};
	int i = 0;
	int key = 0;
	printf("请输入5个正整数:\n");
	for(i = 0;i<5;i++){
		scanf("%d",&s[i]);
	}
	printf("请输入要使用的排序方式(1为升序,0为降序):\n");
	scanf("%d",&key);
	for(i = 0;i<5;i++){
		printf("%d ",s[i]);
	}
	putchar(10);
	my_paixu(s,5,key);

	return 0;
}

作业2:使用main函数传参,实现简易计算器功能

                ./a.out 10 + 20 ---> 30

代码实现:

#include 
int my_add(int,int);
int my_sub(int,int);
int my_mul(int,int);
double my_div(int,int);

int main(int argc, char *argv[])
{
	int ret = 0;
	double ret2 = 0;

	int num1 = 0;
	int num2 = 0;
	char *p1 = argv[1];
	while(*p1){				//遍历转换
		num1 = num1*10 + (*p1-'0');
		p1++;
	}
	char *p2 = argv[3];
	while(*p2){				//遍历转换
		num2 = num2*10 + (*p2-'0');
		p2++;
	}

	if('+' == *argv[2]){
		ret = my_add(num1,num2);
		printf("%d + %d = %d\n",num1,num2,ret);
	}else if('-' == *argv[2]){
		ret = my_sub(num1,num2);
		printf("%d - %d = %d\n",num1,num2,ret);
	}else if('*' == *argv[2]){
		ret = my_mul(num1,num2);
		printf("%d * %d = %d\n",num1,num2,ret);
	}else if('/' == *argv[2]){
		ret2 = my_div(num1,num2);	
		printf("%d / %d = %.3f\n",num1,num2,ret2);
	}
	return 0;
}
int my_add(int x,int y){
	int ret = x+y;
	return ret;
}
int my_sub(int x,int y){
	int ret = x-y;
	return ret;
}
int my_mul(int x,int y){
	int ret = x*y;
	return ret;
}
double my_div(int x,int y){
	double ret = (double)x/(double)y;
	return ret;
}

反思与总结

        第二个周,学的更深,讲的更快了;循环、判断语句,一维数组和二位数组感觉都还可以,对于后面的指针、指针数组、数组指针来说,也都听明白了,可能做练习的时候,还是得在脑子里绕一会,也是因为练习的少,要抽出点时间,找几个练习,公共一下知识,熟练一下逻辑;后面的函数的话,也还可以,和指针结合起来也是不是太熟练,也需要多谢多练,下周不能只听,要拿个本梳理写代码时候的逻辑,题目千变万化,思路和逻辑是统一的。

        写在最后:写这篇文章是笔者一边看老师的目录,一边回想老师讲的内容,仅仅选取了我自己认为比较重要的,或者自己之前没接触过的进行汇总、总结,知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。写的仓促、水平有限,如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!

你可能感兴趣的:(华清远见嵌入式培训,学习总结,c语言,vim,ubuntu,linux)