目录
前言
这是培训的第二个周末,闭眼细想,明显感觉学的东西比上周要多,这周里结束了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 <stdio.h> 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 <stdio.h> 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 <stdio.h> 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 <stdio.h> 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 <stdio.h> 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 <stdio.h>
#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 编码练习
使用二维数组保存杨辉三角,并输出。
#include <stdio.h> 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 代码实现(以降序为例)
#include <stdio.h>
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 <stdio.h> 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 指针和变量和关系
理解与解释:int a 就是我的家,我的家里放着一个快递(10);int *p = &a,此时是指针的定义,*起的作用是标识定义的是一个指针变量,实际上是快递员p在通讯录里存下的我家的地址,不要和*p混了,*p是指快递员p到了我家,拿到了我要寄的快递(10)。
注意:还有几个场景*只起到标识:函数的结构定义里,int *p 也是指定义了一个一级指针,准备接受主函数要传的参数,int **p 是指定义了一个二级指针准备接收其他函数要传的一级指针的地址。
后面这个指针的值传递和地址传递还会细说,只是看到这个地方突然明白了,上课没明白的的一个地方。引上课上的这个练习:
#include <stdio.h>
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 <stdio.h>
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 <stdio.h>
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和操作系统对多字节数据的存储方式可能不一样。
分为小端存储和大端存储。
笔试题:请写一个简单的C语言程序,来判断你使用的主机是大端存储还是小端存储。
#include <stdio.h> 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+不确定的值;
分析:
1.2 指针和一维数组
对照老师在程序中举得例子,一目了然:
#include <stdio.h>
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 <stdio.h>
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 <stdio.h>
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 <stdio.h> 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 <stdio.h> 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 指针和字符串
首先讲了虚拟内存,虚拟内存的空间划分:
注意几点:
1、字符数组定义在 栈区,里面存的字符串常量在 字符串常量区;可以通过数组操作它所存储的字符串。
2、指针定义在 栈区,指针指向的字符串在 字符串常量区;无法直接修改 字符串常量区的内容。
#include <stdio.h>
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 二级指针
二级指针是用来保存一级指针的地址的。
二级指针多用于 将一级指针的地址作为函数的参数传递时。
知道这几点就够了,例子在一级指针里分析过了,注意这里的 * 是起到的标识作用。
周五
今天是课前讲题幸运儿,讲了前一天老师留的课堂作业:
编码:实现 atoi 函数的功能。--字符串转
char str[32] = {0};
scanf("%s", str);//1234
//你的操作
printf("%d");//1234
思路:循环 * 10 + 下一位数字#include <stdio.h> 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 <stdio.h>
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 <stdio.h>
//有参数的函数声明
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 <stdio.h>
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 <stdio.h> 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 <stdio.h> 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 <stdio.h>
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 <stdio.h>
//遍历一维数组的函数
//数组传参的写法:写指针即可----常用的写法
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 <stdio.h>
//遍历二维数组的函数
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函数的参数
这块虽说是听明白了,但是没太理解是用来干啥的,作业能写出来,感觉似懂非懂。
#include <stdio.h>
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 <stdio.h> 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 <stdio.h> 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; }
反思与总结
第二个周,学的更深,讲的更快了;循环、判断语句,一维数组和二位数组感觉都还可以,对于后面的指针、指针数组、数组指针来说,也都听明白了,可能做练习的时候,还是得在脑子里绕一会,也是因为练习的少,要抽出点时间,找几个练习,公共一下知识,熟练一下逻辑;后面的函数的话,也还可以,和指针结合起来也是不是太熟练,也需要多谢多练,下周不能只听,要拿个本梳理写代码时候的逻辑,题目千变万化,思路和逻辑是统一的。
写在最后:写这篇文章是笔者一边看老师的目录,一边回想老师讲的内容,仅仅选取了我自己认为比较重要的,或者自己之前没接触过的进行汇总、总结,知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。写的仓促、水平有限,如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!