# c_workspace **Repository Path**: li-kewei123/c_workspace ## Basic Information - **Project Name**: c_workspace - **Description**: 常见的计算机语言:C、C++、Java、Python等。 C++、Java编译器解释器、市面上很多的软件如WPS、Linux操作系统等都是基于C语言来实现的,C语言作为一门计算机语言,是人和计算机交流的语言,用来告诉计算机怎么工作。 C语言前三永不过时。倡导人人都学点编程。 编译环境:使用VS2019以上的版本,例如VS2019,拒绝使用VC++6.0、DEV-C++这样的工具,因为太OUT啦。 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 2 - **Created**: 2022-04-19 - **Last Updated**: 2025-06-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: C语言 ## README C语言从零基础到精通!1 <<<<<<< HEAD >>>>>>> ec30aabe4f1eda3857c037b9b4ab8efd354febac >>>>>>> 799be5584cef4b271d6eff6f39d2ff0a3e04f31b # 四、选择语句、分支语句、选择语句 ```c #include int main() { int a = 0; ;//是语句-空语句 return 0; } ``` ```c #include #include int main() { int input = 0; printf("加入比特\n"); printf("你要好好学习吗?(1/0)>:"); scanf("%d",&input); if (input == 1) printf("同学你好,给你一个好offer!\n"); else printf("同学你好,继续加油!\n"); return 0; } ``` 人生的选择,生活的选择无非就是如下几种: ![](images\c00023.png) ## 1、单分支if ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int age = 8; if (age < 18) printf("你是未成年!\n");//"你是未成年! return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int age = 28; if (age < 18) printf("你是未成年!\n");//什么都不执行 return 0; } ``` ```c //例题:将三个数字从大到小输出 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 0; int b = 0; int c = 0; scanf("%d%d%d",&a,&b,&c);//%d#%d#%d 格式是什么,键盘打印就是什么 不然没用 建议什么都不用加 //空格就行 中英文逗号也是有区别的 //算法实现:a中放最大值 b次之 c中放最小值 if (a < b)//较大值 { int tmp = a; a = b; b = tmp; } if (a < c) { int tmp = a; a = c; c = tmp; } if (b < c) { int tmp = b; b = c; c = tmp; } printf("%d%d%d\n",a,b,c); return 0; } ``` ```c 例题:判断1-99中出现多少个9 方法一:自己写的 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int count = 0; for (i = 0; i <= 100; i++) { //判断i里面有没有9 if (i % 10 == 9 || i / 10 == 9) { printf("%d ", i); count++; } } count += 1; printf("\n在1-100中共有%d个9\n", count);//在1-100中共有20个9 return 0; } 方法二:老师写的 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int count = 0; for (i = 1; i <= 100; i++) { if (i % 10 == 9) { printf("%d ",i); count++; } if (i / 10 == 9) { printf("%d ",i); count++; } } printf("\n在1-100中总计出现%d次9\n",count);//在1-100中总计出现20次9 return 0; } ``` ```c 计算1/1-1/2+1/3-1/4+1/5...+1/99-1/100的值。 代码1:没有带正负号 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; double sum = 0.0;//和也应该放到浮点数里面去 for (i = 1; i <= 100; i++) { //错误代码,导致结果为 sum += 1 / i;//必须保证除号两边必须有一个是浮点数 sum += 1.0 / i;//因为i已经是整型了 }//1/1+1/2(商0余1)+1/3(商0余1)... //1+0+0+0= 1 printf("%lf\n",sum);//打印的时候,也不能用%d了,用%lf,因为打印的是浮点数 return 0;//5.187378 } 代码2:带上正负号 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; double sum = 0.0; int flag = 1; for (i = 1; i <= 100; i++) { sum += flag * 1.0 / i; flag = -flag; } printf("%lf\n",sum);//0.688172 return 0; } 代码3:奇数项+偶数项 ``` ## 2、双分支if...else ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int age = 23; if (age < 18) printf("未成年"); else printf("成年");//成年 return 0; } ``` ## 3、多分支if...else if...else ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int age = 23; if (age < 18) printf("你还是未成年"); //else if(18 <= age < 28) 错误写法 //按照上面的写青年依旧会执行,因为先执行18<=23是正确的得到1(正确是1,错误是0) //但是1依旧是<18,所以代码执行 else if (age >= 18 && age < 28)//标准写法 printf("恭喜你,成为青年"); else if (age >= 28 && age < 50)//标准写法 printf("恭喜你,成为壮年"); else if (age >= 50 && age < 90)//标准写法 printf("恭喜你,成为老年"); else//最后一个else可以省略,也可以留着 printf("老不死"); return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int age = 153; if (age < 18) printf("未成年\n"); else { if (age >= 18 && age < 28) printf("青年\n"); else if (age >= 28 && age < 50) printf("壮年\n"); else if (age >= 50 && age < 90) printf("老年\n"); else printf("老不死\n"); } return 0; } 代码3: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int age = 23; if (age < 18) {//注意一定要加该{语句块,代码块},不加的话很容易报错 printf("你还未成年!\n"); printf("不可以谈恋爱\n"); } else { printf("你已经成年啦!\n");//执行该语句 printf("可以谈恋爱啦!\n");//执行该语句 } } ``` ```c 例题1:打印1000-2000年的润年 //方法一: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { //判断是否为闰年: //1、能被4整除并且不能被100整除是闰年 //2、能被400整除是闰年 int y = 0; int count = 0; for (y = 1000; y <= 2000; y++) { if (y % 4 == 0 && y % 100 != 0) { printf("%d ", y); count++; } else if (y % 400 == 0) { printf("%d ", y); count++; } } printf("\ncount = %d\n",count);//243 return 0; } //方法二: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int year = 0; int count = 0; for (year = 1000; year <= 2000; year++) { if (((year%4==0)&&(year%100!=0))||(year%400==0)) { printf("%d ", year); count++; } } printf("\n闰年的年份有%d年\n",count); return 0; } ``` ```c 例题2:写出100到200的素数 改进前: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int count = 0; for (i = 100; i <= 200; i++) { //判断是否为素数 //素数判断的规则 //1、试除法 //产生2->i-1 int j = 0; for (j = 2; j < i; j++) { if (i % j == 0) break; } //两种情况会跳出来这里(第一:break跳出 第二:判断条件不满足跳出) if (j == i) { count++; printf("%d ",i); } } printf("\ncount = %d\n",count);//count = 21 return 0; } 改进后: #define _CRT_SECURE_NO_WARNINGS 1 #include #include //sqrt -开平方的数学库函数 int main() { int i = 0; int j = 0; int count = 0; for (i = 100; i <= 200; i++) { int j = 0; for (j = 2; j <= sqrt(i); j++) { if (i % j == 0) { break; } } if (j > sqrt(i)) { count++; printf("%d ",i); } } printf("\ncount = %d\n", count);//count = 21 return 0; } 再优化: #define _CRT_SECURE_NO_WARNINGS 1 #include #include //sqrt -开平方的数学库函数 int main() { int i = 0; int j = 0; int count = 0; for (i = 101; i <= 200; i+=2) { int j = 0; for (j = 2; j <= sqrt(i); j++) { if (i % j == 0) { break; } } if (j > sqrt(i)) { count++; printf("%d ",i); } } printf("\ncount = %d\n", count);//count = 21 return 0; } ``` ## 4、悬空else else与离它最近的未匹配的if匹配 ![](images\c00024.png) ```c 修改前: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 0; int b = 2; if (a == 1) if (b == 2) printf("hehe\n");//什么都不执行 else printf("haha\n");//什么都不执行 return 0; } 修改后: #define _CRT_S ECURE_NO_WARNINGS 1 #include int main() { int a = 0; int b = 2; if (a == 1) { if (b == 2) printf("hehe\n"); } else printf("haha\n");//haha return 0; } ``` ## 5、代码规范问题 ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int num = 4; if (num = 5) { printf("likewei\n");//likewei } return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int num = 4; if (5 == num)//=写少了,编译不过去 常量==变量 { printf("likewei\n"); } return 0; } ``` ## 6、判断一个数是否为奇数 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 38; if (a % 2 == 1) printf("%d为奇数\n", a); else { printf("%d为偶数\n",a); } return 0; } ``` ## 7、输出1-100之间的奇数 ```c 代码1: //输出1-100内的所有奇数 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { for (int i = 1; i <= 100; i++) { if (i % 2 == 1) { //printf("第%d个数为奇数\n", i); printf("%d\t", i); } } return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; while (i <= 100) { if (i % 2 == 1) printf("%d\t", i); i++; } return 0; } 代码3: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; while (i <= 100) { if (i % 2 != 0) printf("%d\t", i); i++; } return 0; } 代码4: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; while (i <= 100) { printf("%d\t", i); i+=2; } return 0; } ``` ``` 作业: 1、在switch语言中的关键字不包含哪个?a a、continue(循环中出现) b、break c、default d、if 2、if语句是一种分支语句,可以实现单分支和多分支。 3、if语句中0表示假,非0表示真。 ``` ## 8、switch...case多分支语句 **(1)写出1-7所对应的星期** ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { int day = 0; scanf("%d", &day); if (1 == day) printf("星期一\n"); else if(2 == day) printf("星期二\n"); else if (3 == day) printf("星期三\n"); else if (4 == day) printf("星期四\n"); else if (5 == day) printf("星期五\n"); else if (6 == day) printf("星期六\n"); else printf("星期日\n"); } 代码2:case(随机选择)决定入口,break决定出口 #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { //float day = 3.0;//定义switch语句只能是整型 报错 int day = 0; int n = 2; scanf("%d", &day); switch (day)//switch进行判断 {//case入口进去 case 1: printf("星期一\n"); break; //case 2: 非法的case表达式 不能写成浮点型 //case 1 + 0: 编译成功等表达式 //case n: 错误,必须是常量,报错 case 2: printf("星期二\n"); break; case 3: printf("星期三\n"); break; case 4: printf("星期四\n"); break; case 5: printf("星期五\n"); break; case 6: printf("星期六\n"); case 7: printf("星期日\n"); } } 代码3:输入1-5是工作日,输入6-7是休息日 用上面的代码,也可以只是太啰嗦。用下面的方法更容易。 #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { int day = 0; int n = 1; scanf_s("%d", &day); switch (day) { case 1: if (n == 1)//switch语句可以出现if语句,但是不能出现continue,因为没得任何意义(不知道调到哪里去)。 printf("haha\n"); case 2: case 3: case 4: case 5: printf("工作日\n"); break; case 6: case 7: printf("休息日\n"); break;//虽然不需要了,但也要加上,为后人写代码补充提供良好的体验 default://未来可有可无。 //case和default位置没有影响,随便放他们的位置均可。建议default放到后面 printf("输入错误\n");//写default的时候,里面内容什么都不写,写一个break就行。 break; } return 0; } 代码4: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int n = 1; int m = 2; switch (n)//外层入口 { case 1: m++; case 2: n++; case 3: switch (n)//内层入口 {//switch允许嵌套使用 case 1: n++; case 2: m++; n++; break;//退出内层 } case 4: m++; break;//退出外层 default: break; } printf("m = %d, n = %d\n", m, n);//m = 5, n = 3 return 0; } ``` ```c 总结: 1、switch语句中default子句可以放在任意位置。 2、switch语句中case表达式不要求顺序。 3、switch(c)语句中case后的表达式只能是整型常量表达式。常量表达式例如:int\long\char(注意不能是float) 4、 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int x = 3; int y = 3; switch (x % 2) { case 1: switch (y) { case 0: printf("first"); case 1: printf("second"); break; default:printf("hello"); } case 2: printf("third");//hellothird } return 0; } ``` ``` 考研: #define _CRT_SECURE_NO_WARNINGS #include int main() { int i = 0; while (scanf("%d", &i) != EOF) { if (i > 0)//在if的括号后面不可以加分号; 会造成表达式无论真假,都会执行后面的语句 {//如果不加{},如果有两句以上,第一句之后的语句跟if语句是无关的 保证关联性 printf("i is bigger than 0\n"); } else { printf("我喜欢你\n"); } } return 0; } //Ctrl+z连续按住三次就可以退出死循环代码 ``` # 五、循环语句 ## 1、不显示行号: ```c #include int main() { int line = 0; printf("加入比特!\n"); //如下两行为重复操作的东西: //printf("敲一行代码\n"); //line++; //用循环语句 while (line < 20000) { printf("敲一行代码\n"); line++; } printf("好offer到手啦"); return 0; } ``` ## 2、显示第几行 &判断>=20000行: ```c #include int main() { int line = 0; printf("加入比特!\n"); //如下两行为重复操作的东西: //printf("敲一行代码\n"); //line++; //用循环语句 while (line < 20000) { printf("敲一行代码: %d\n",line); line++; } if(line>=20000) printf("好offer到手啦"); return 0; } ``` ## 3、if语句和while语句比较 ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { if (1)//1就是条件为真(除了0都是条件为真) printf("hahha\n");//hahha return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { while (1)//条件为真 printf("hahha\n");//hahha死循环 return 0; } while执行原理: while(表达式) //表达式为真,执行循环语句; 循环语句statement语句; //执行完之后,回去判断表达式是否为真(真继续执行,假0终止执行) ``` ## 4、在屏幕上打印1-10的数字 ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; printf("%d\t", i); i++; return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; while (i <= 10) { printf("%d\t", i); i++; } return 0; } 代码3: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; while (i <= 10) { if (i == 5) break;//停止循环,跳出循环 printf("%d\t", i);//1 2 3 4 i++; } return 0; } 代码4: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; while (i <= 10) { if (i == 5) continue;//继续上面判断循环 printf("%d\t", i);//1 2 3 4 死循环 i++; } return 0; } 代码5: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; while (i <= 10) { i++; if (i == 5) continue;//跳过本次循环之后的代码,去判断 printf("%d\t", i);//1 2 3 4 6 7 8 9 10 11 } return 0; } ``` ```c 例题:辗转相除法的妙用:24%6=0 6就是最大公约数 24%18=6 18%6=0 6就是最大公约数 //两个数求最大公约数(建议使用辗转相除法) #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int m = 24; int n = 18; int r = 0; scanf("%d%d",&m,&n); while (r = m % n) { m = n; n = r; } printf("%d\n",n); return 0; } ``` ## 5、getchar和putchar的使用 ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int ch = getchar();//接收一个键盘的字符,会返回一个字符,放到ch里面 printf("%c\n", ch); putchar(ch);//输出键盘里的字符,让其打印在屏幕上,和printf一样 return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int ch = 0; while ((ch = getchar()) != EOF)//Ctrl+z == EOF才能结束 { putchar(ch);//^Z 输出函数 } return 0; } //EOF -end of file文件结束标志 值为-1 //想要查看EOF,就选中EOF,鼠标右击,查看定义。将来学文件的时候用得到 代码3: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { printf("%d\n", '\n');//'\n'的ASCII码值是10 return 0; } 代码4:用武之地 #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { int ret = 0; char password[20] = { 0 }; printf("请输入密码:>"); scanf("%s",password); printf("请确认(Y/N):>");//输入密码,并存放在password中 ret = getchar(); if (ret == 'Y') { printf("确认成功\n"); } else { printf("放弃确认\n"); } return 0; } ``` ![](images\c00025.png) ```c 代码5: #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { int ret = 0; char password[20] = { 0 }; printf("请输入密码:>"); scanf("%s", password); //缓冲区还剩余一个'\n' //读取一下'\n' getchar(); printf("请确认(Y/N):>"); ret = getchar(); if (ret == 'Y') { printf("确认成功\n"); } else { printf("放弃确认\n"); } return 0; } ``` ![](images\c00026.png) ```C 代码6:scanf只会把空格前面的东西读走。空格也属性字符,为空字符。 #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { int ret = 0; int ch = 0; char password[20] = { 0 }; printf("请输入密码:>"); scanf("%s", password); while ((ch = getchar()) != '\n') { ;//空语句 } printf("请确认(Y/N):>"); ret = getchar(); if (ret == 'Y') { printf("确认成功\n"); } else { printf("放弃确认\n"); } return 0; } 代码8: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int ch = 0; while ((ch = getchar()) != EOF) { if (ch < '0' || ch > '9') continue; putchar(ch); } return 0; } ``` ## 6、for循环 循环使用频率的对比:for>while>do...while(现在很多的计算机语言都直接for循环了,省去了while循环等) **while循环的弊端之处** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0;//初始化 while (i < 10)//判断 { //.......statement i++;//调整 } return 0; } ``` ## 7、for循环打印1-10个数 ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; //初始化 判断 调整 for (i = 1; i <= 10; i++) { printf("%d ", i);//1 2 3 4 5 6 7 8 9 10 } return 0; } ``` ![](images\c00027.png) ```c break的应用: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; for (i = 1; i <= 10; i++) { if (i == 5) break; printf("%d ", i);//1 2 3 4 } return 0; } continue的应用: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; for (i = 1; i <= 10; i++) { if (i == 5) continue; printf("%d ", i);//1 2 3 4 6 7 8 9 10 } return 0; } ``` ![](images\c00028.png) ## 8、不可在for 循环体内修改循环变量,防止 for 循环失去控制 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; for (i = 0; i < 10; i++) { if (i = 5)//在循环体内改变循环变量,一般对于i的调整,不要放在循环体内部 printf("haha\n");//死循环 printf("likewei\n");//死循环 } return 0; } ``` ## 9、 建议for语句的循环控制变量的取值采用“前闭后开区间”写法 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int iSS = 0; //for (i = 0; i <= 9; i++) 不建议 for (i = 0; i < 10; i++)//前闭后开区间 的写法 建议 意义:10次循环、10次打印、10个元素 不绝对 { printf("%d ", arr[i]);//1 2 3 4 5 6 7 8 9 10 } return 0; } ``` ```c 例题1(待改进):该代码存在问题 //求10个整数的最大值 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3036,40000,5,6,7,8,9,10,100 }; int max = 0;//最大值 int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { if (arr[i] > max) { max = arr[i]; } } printf("本组数中最大值为:%d",max);//本组数中最大值为:40000 return 0; } ``` ![](images\c00035.png) ```c 代码2(改进完成): #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = {-1,-2,-3,-4,-5,-6,-7,-8,-9,-10}; int max = arr[0];//修改处1 ,比较的那个数从数组中获取,不引用第三方数字 int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 1; i < sz; i++)//初始值从第二个抽取出来比较 {//所以i不要等于第一个数字,即i = 1(防止本身和本身进行比较) if (arr[i] > max) { max = arr[i]; } } printf("本组数中最大值为:%d", max); return 0; } ``` ## 10、for循环变种一 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { for (;;) { printf("likewei\n");//死循环 } return 0; } ``` ![](images\c00029.png) ```c 代码1:打印100个likewei #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int j = 0; for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { printf("likewei\n");//打印100个liikewei } } return 0; } 代码2:打印10个likewei 随便省略带来的后果 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int j = 0; for (; i < 10; i++) { for (; j < 10; j++) {//内部循环结束第一次,出来后j=10不会被销毁,外部循环第二次就无法进行 printf("likewei\n");//打印100个liikewei } } return 0; } ``` ![](images\c00030.png) ```c 代码3:改进方法 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int j = 0; for (; i < 10; i++) { for (j = 0; j < 10; j++) { printf("likewei\n");//打印100个liikewei } } return 0; } ``` **在屏幕上输出一个乘法口诀表:** ```c 方法一:待改进 //打印乘法口诀表 //1*1=1 //2*1=2 2*2=4 //3*1=3 3*2=6 3*3=9 //4*1=4 4*2=8 4*3=12 4*4=16 //......... //9*1=9 //总结:打印9行(a表示),打印9列(b表示) #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; //确定打印9行 for (i = 1; i <= 9; i++) { //打印一行的信息 int j = 1; for (j = 1; j <= i; j++) { printf("%d*%d=%d ",i,j,i*j); } printf("\n"); } return 0; } 方法二:改进完成(因为第三列没有对齐完成) #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; for (i = 1; i <= 9; i++) { int j = 1; for (j = 1; j <= i; j++) { printf("%d*%d=%-2d ",i,j,i*j);//%d*%d=%2d右对齐 %d*%d=%-2d左对齐 }//%2d就代表两位数-打印两位的意思-不够两位会自动用空格补齐 printf("\n"); } return 0; } ``` ![](images\c00036.png) **在屏幕上输出一个乘法口诀表:** ```c #define _CRT_SECURE_NO_WARNINGS #include void print_table(int n) { int i = 0; for (i = 1; i <= n; i++) { int j = 0; for (j = 1; j <= i; j++) { printf("%d*%d=%-3d ",i,j,i*j); } printf("\n"); } } int main() { int n = 0; scanf("%d", &n); print_table(n); return 0; } ``` ## 11、for循环变种二 逗号表达式:逗号隔开的表达式 用两个循环变量来控制的变量 ## 12、面试题 k条件那里写的不是判断语句,是赋值语句。 ```c 代码1:循环0次 //请问循环要循环多少次? #include int main() { int i = 0; int k = 0; for(i =0,k=0; k=0; i++,k++)//0就是假,其他数字就是真。 k++; return 0; } 代码2:死循环 //请问循环要循环多少次? #include int main() { int i = 0; int k = 0; for(i =0,k=0; k=1; i++,k++)//非0都是死循环 k++; return 0; } ``` ## 13、do...while循环打印1-10的数字 do...while至少循环一次 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; do { printf("%d ", i); i++; } while (i <= 10);//1 2 3 4 5 6 7 8 9 10 return 0; } ``` ![](images\c00031.png) ## 14、do...while遇到break ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; do { if (i == 5) break; printf("%d ", i);//1 2 3 4 i++; } while (i <= 10); return 0; } ``` ## 15、do...while遇到continue ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 1; do { if (i == 5) continue; printf("%d ", i);//1 2 3 4 死循环(什么都不打印 闪光点) i++; } while (i <= 10); return 0; } ``` ## 16、循环作业 **题一:** ```c 1、计算一个n的阶乘: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int n = 0; int ret = 1;//初始值不能给0(给其再乘个数字依旧是0) scanf("%d",&n);//不考虑溢出的情况,例如100的阶乘(在一个整型里面可能就放不下了),结果会错误。 for (i = 1; i <= n; i++) { //产生得到的1、2、3、4等得累计到一起。 ret = ret * i; } printf("ret = %d\n",ret);//ret = 120 return 0; } ``` **题二:** ```c 2、计算 1!+2!+3!+……+10! 改进前:没实现效果 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int n = 0; int ret = 1; int sum = 0; for (n = 1; n <= 3; n++)//用3代替可以口算,方便检验 { for (i = 1; i <= n; i++) { ret = ret * i; }//n的阶乘 sum = sum + ret;//累计相乘 } printf("sum = %d\n",sum);//sum = 15 1+2+6=9 return 0; } //ret = 1*1 =1 //ret = 1*1*2 = 2 //ret = 2*1*2*3 = 9(本来等于6就对了) 改进后:效果不太好,不用累计乘,不用每次都从1开始乘以 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int i = 0; int n = 0; int ret = 1; int sum = 0; for (n = 1; n <= 3; n++) { ret = 1;//重置赋值为1 for (i = 1; i <= n; i++) { ret = ret * i; } sum = sum + ret; } printf("sum = %d\n",sum);//sum = 9 return 0; } 优化后:效果倍增 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int n = 0; int ret = 1; int sum = 0; for (n = 1; n <= 3; n++) { ret = ret * n; sum = sum + ret; } printf("sum = %d\n",sum);//sum = 9 return 0; } ``` **题三:** ```c 3、在一个有序数组中查找具体的某个数字n。(讲解二分查找) 改进前:不够高效 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int k = 7; //写一个代码,在arr数组(有序的)中找到7 int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { if (k == arr[i]) { printf("找到了,下标是 %d\n",i);//找到了,下标是 6 break; } } //break调到此处 if (i == sz) printf("找不到\n"); return 0; } ``` ![](images\c00032.png) ```c 改进后:二分(折半)查找算法 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int k = 7; int sz = sizeof(arr) / sizeof(arr[0]);//计算元素个数 int left = 0;//表示左下标 int right = sz - 1;//表示右下标 while (left <= right) { int mid = (left + right) / 2; if (arr[mid] > k) { right = mid - 1; } else if (arr[mid] < k) { left = mid + 1; } else { printf("找到了,下标是:%d\n", mid); break; } } if (left > right) { printf("找不到了\n"); } return 0; } ``` **题四: 编写代码,演示多个字符从两端移动,向中间汇聚。 **![](images\c00034.png) ```c #define _CRT_SECURE_NO_WARNINGS 1 #include #include //strlen的头文件 #include //Sleep #include //system int main() { char arr1[] = "welcome to bit!!!"; char arr2[] = "#################"; int left = 0;//第一个字母下标 //法一: //int right = sizeof(arr1) / sizeof(arr1[0]) - 2;//最后一个字母下标(因为减1得到最后一个字符下标,因为最后有个\0) //法二: int right = strlen(arr1) - 1;//该函数个数里面是不包含/0的 while (left <= right) { arr2[left] = arr1[left]; arr2[right] = arr1[right]; printf("%s\n", arr2); //休息一秒 Sleep(1000);//停顿1秒,1000毫秒 system("cls");//执行系统命令的 cls清空屏幕 left++; right--; } printf("%s\n", arr2); return 0; } ``` **题五: 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则 提示登录成,如果三次均输入错误,则退出程序。** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include #include //strcmp的头文件 int main() { int i = 0; char password[20] = { 0 }; for (i = 0; i < 3; i++) { printf("请输入密码:>\n"); scanf("%s",password); //if (password == "123456")//==不能用来比较两个字符串是否相等,应该使用一个库函数- strcmp if (strcmp(password,"123456") == 0)//strcmp 如果第一个字符串>第二个字符串:返回一个>0的数字 {//strcmp 如果第一个字符串<第二个字符串:返回一个<0的数字 printf("登录成功\n"); break; } else { printf("密码错误\n"); } } if (i == 3) { printf("三次密码均错误,退出程序\n"); } return 0; } ``` ## 17、goto语句 结构化程序**尽可能少使用**goto转到语句。 去哪里,不建议大家使用,尽量使用替代品。 执行流程被打断。 特别会产生bug ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { again: printf("hello likewei\n");//死循环该代码 goto again;//在一次循环 return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { printf("hello likewei\n");//打印 goto again; printf("你好!\n"); again: printf("haha\n");//打印 return 0; } ``` # 六、函数 **2、两数比较最大值** 改进前: ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int num1 = 10; int num2 = 20; if (num1 > num2) { printf("较大值为 %d\n", num1); } else { printf("较大值为 %d\n", num2); } return 0; } ``` 改进后:use函数 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int Max(int x,int y) {//函数体 if (x > y) { return x; } else { return y; } } int main() { int num1 = 10; int num2 = 20; int max = 0; max = Max(num1, num2); printf("较大值是:%d\n", max); return 0; } ``` 一般而言,百度百科解释的不够专业,维基百科更加专业 C语言早期出现的时候是没有printf和scanf的 只有一些基本的语法,例如if语句,for循环等 函数时没有的,没有strlen()、strcpy()求字符串长度的(操作字符串)等 开发效率比较低,很多人都要使用的。C语言提供一组公用的东西。 库函数就应运而生啦!库函数都是要引入头文件才能使用的 ``` stdio.h头文件下的函数: remove 删除函数 rename 重命名 fclose 关闭文件 fopen 打开文件 scanf 输入函数 printf 打印函数 ``` ```c memset内存操作函数 time时间函数 sqrt开平方数学函数 #include //strlen -string length-跟字符串长度有关的函数-求字符串长度 ``` ## 1、strcpy函数 ```c 文档说明: 定义: char * strcpy ( char * destination, const char * source ); char* 指针类型 指针是用来存放地址的 detination 目的地 存放一个地址过去 source源头 char * source源头地址 字符串拷贝函数:把源头拷贝到目的地 Copy string 拷贝字符串 Copies the C string pointed by source(通过source指向的字符串) into the array pointed by destination, including the terminating null character (and stopping at that point). 拷贝C字符串 pointed(被指向的)into到 array数组 including 包含 teminating 结束 null '\0'或\0 character 字符 意味着我们拷贝的时候\0都拷贝过去啦 To avoid overflows, the size of the array pointed by destination shall be long enough to contain the same C string as source (including the terminating null character), and should not overlap in memory with source. Parameters参数,形参 destination Pointer指针 to the destination array目的地数组 where the content内容 is to be copied覆盖. source C string to be copied被拷贝. Return Value返回值 destination is returned.返回目的地 Example例子 /* strcpy example */ #include #include int main () { char str1[]="Sample string"; char str2[40]; char str3[40]; strcpy (str2,str1); strcpy (str3,"copy successful"); printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include //cstring是c++的一种引法 #include //c-language的引法 int main() { //strcpy -string copy- 字符串拷贝 包含:要拷贝的内容、拷贝到哪里(目的地) char arr1[] = "bit";//字符串的结束标志是\0 char arr2[20] = "########";//\0拷贝过去直接调试就能看到 //为啥拷贝过去的时候,bit后面的#就没有啦 //拷贝过去 目的地就会放 bit\0####### //以%s打印的时候'b' 'i' 't' '\0' 字符串的结束标志-别的其他内容都不打印了 //但凡用到字符串-求字符串长度-打印字符串的时候 strcpy(arr2,arr1);//指针类型,最后会返回一个地址 printf("%s\n",arr2);//bit return 0; } ``` ## 2、memset函数 谷歌翻译出来的一般不够准确,局部单词可以查 ```c 文档说明: void * memset ( void * ptr, int value, size_t num ); 第一个参数:ptr 第一个参数类型:void* int* float* double* 指针-用来放地址的 第二个参数:value值 第三个参数:size_t num size_t大小 int的t是一个整型 num是一个值 Fill填充 block of memory 内存块 设置内存块 Sets设置 the first刚刚开始的 num bytes字节 of the block of memory pointed指向 by ptr to the specified特殊 value (interpreted as an unsigned char). 设置内存块刚开始的num空间, 把ptr所指向的num前几个大小设置为value的内容 Parameters参数介绍 ptr Pointer to the block of memory to fill.指针指向需要填充的内存块 value Value to be set(value需要被设置). The value is passed as an int, but the function fills the block of memory using the unsigned char conversion of this value. num Number of bytes to be set to the value.要被设置的字节个数 size_t is an unsigned integral type. Libraries 库 ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { //memset函数名-memory记忆、内存 set设置 char arr[] = "hello world";//把hello中5个字符改成##### //内存设置 memset(arr,'#',5);//主动放#号,并未放/0进去 //'#'在内存中存的是ASCII码值 传参传过去的都是ASCII码值-int完整匹配起来 //放个字符串进去是不行的 printf("%s\n",arr); return 0; } //放进去的字符串比原来长,必然会溢出。所以要保证。不然写了个bug. //公司里面有自己的C语言库-英文的-我们需要阅读-阅读文档能力 ``` ## 3、自定义函数 函数可以传值调用,传值调用的时候形参是实参的一份临时拷贝,改变形参不影响实参。 函数可以传址调用,传址调用的时候,可以通过形参操作实参。 函数可以嵌套调用,但不能嵌套定义。 函数调用的时候,全局变量可以用在函数间的数据传递,通常情况下不采用,因为看起来比较戳。 函数必须保证先声明,后使用。函数的定义就是说明函数时怎么实现的。 函数设计应该追求高**内聚**(内聚:代码相对独立,与其他联系少或没联系。高内聚:更加的独立)、低**耦合**(耦合:我们俩之间尽量能够交互,关联性更强一些。低耦合:关联尽量少一些)。我自己本身的功能不会包含别人的功能。 在写代码的时候,尽量少的去使用全局变量。全局变量在我们工程里面哪里都可以使用,哪里都可以修改,尽量少使用,避免乱套,不至于让代码失控。 函数的参数不宜过多,最好不要超过4个,最多的5个就够了,十多个就特别麻烦了。 设计函数时,尽量做到谁申请的资源就由谁来释放。我自己的申请的,不用的就回收了,不要申请了不用了还不告诉别人,等着别人去收拾他,万一没有回收,就会导致资源的浪费。 函数的返回值可以是空的,就是void,不需要返回的意思。 **(1)写一个函数可以找到两个整数中的较大值** ```c //写一个函数可以找到两个整数中的较大值 #define _CRT_SECURE_NO_WARNINGS 1 #include //printf的头文件 //定义函数 int get_max(int x, int y)//get_max返回一个整型 { if (x > y) { return x; } else { return y;//返回整型 } } int main() { int a = 50; int b = 20; int max1 = 0; //函数的使用 int max = get_max(a,b); printf("max = %d\n",max); max1 = get_max(200, 300);//直接调用也行 注意要传递一个确切的值 可以是表达式299+1 //max1 = get_max(200, get_max(2, 30)); 函数的嵌套 printf("max1 = %d\n", max1);//max1 = 300 return 0; } ``` ![](images\c00037.png) ```c 例题:写一个函数可以交换两个整形变量的内容 方法一:临时变量的方法: //写一个函数可以交换两个整形变量的内容 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10; int b = 20; int tmp = 0; scanf("%d%d",&a,&b); printf("交换前:a = %d b = %d\n",a,b); tmp = a; a = b; b = tmp; printf("交换后:a = %d b = %d\n", a, b); return 0; } 方法二:啥搜素显示不了。交换前后不起作用。因为传递进去的位置改变了。里面x和y是单独的一个位置。 x和y没有半毛钱关系了。不能完成任务。 当实参传给实参的时候,形参其实是实参的一份临时拷贝,对形参的修改时不会改变实参的。 ``` ```c 案例导入: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10;//开辟了4个字节的空间 int* pa = &a;//取地址a实际上拿出来是16进制的数字 pa的类型就是int*指针类型的指针变量 *pa = 20;//pa里面存放a的地址 加个*就是解引用操作 *pa找到a printf("%d\n",a); return 0; } ``` ```c 方法三:指针交换两个数的值 #define _CRT_SECURE_NO_WARNINGS 1 #include void Swap(int* pa,int*pb)//指针变量是用来接收地址的 形参-形式上的参数-是存在的-实际上没有实际的空间 { int tmp = 0; tmp = *pa; *pa = *pb; *pb = tmp; } int main() { int a = 10; int b = 20; printf("交换前:a = %d b = %d\n", a, b); //调用函数 Swap(&a,&b);//告诉我的地址,通过地址找回来 实参-真实传递给函数的参数 printf("交换后:a = %d b = %d\n", a, b); return 0; } ``` ![](images\c00040.png) **(2)写一个函数可以判断一个数是不是素数。** ```c //打印100-200之间的素数 #define _CRT_SECURE_NO_WARNINGS 1 #include #include //是素数返回1,不是素数返回0 int is_prime(int n) {//产生2- n-1的数字来测试下n int j = 0; for (j = 2; j <= sqrt(n); j++) { if (n % j == 0) { //printf("%d不是素数",n); return 0;//return在这里一执行,直接该is_prime直接结束。break执行只会跳出该循环。 } //方法一: //if(j == n) // return 1; //方法二: return 1; } } int main() { int i = 0; for (i = 100; i <= 200; i++) { //判断i是否为素数 if(is_prime(i) == 1) printf("%d\t", i); } return 0; } ``` **2、写一个函数判断一年是不是闰年。** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int is_leap_year(int y) { if (y % 4 == 0 && y % 100 != 0 || y % 400 == 0) return 1; else return 0; } int main() { int year = 0; for (year = 1000; year <= 2000; year++) { //判断year是否为闰年 if (is_leap_year(year) == 1) { printf("%d ",year); } } return 0; } 总结: 可移植性:一个平台转义到另外一个平台 可复用性:多次使用该函数 函数写法:干净利落 ``` **3、 写一个函数,实现一个整形有序数组的二分查找。** ```c #define _CRT_SECURE_NO_WARNINGS 1 //二分查找 //在一个有序数组中查找具体的某个数 //如果找到返回该数的下标,找不到返回-1 #include int binary_search(int arr[], int k,int sz)//形参和实参的名字相同是不影响的 //数组只会存过去首元素的地址 //不要看他人模狗样儿的写个数组,实际本质上是个指针(接收地址) { int left = 0; int right = sz - 1; //多次查找 while (left <= right) { //一次查找 //算法的真正实现 int mid = (left + right) / 2;//中间元素的下标 if (arr[mid] < k) { left = mid + 1; } else if (arr[mid] > k) { right = mid - 1; } else { return mid; } } return -1; } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int k = 7; int sz = sizeof(arr) / sizeof(arr[0]);//4or8 sizeof(arr[0])整型也是4 得到1 int ret = binary_search(arr, k,sz);//arr传递过去只会传递arr[0]地址过去 if (ret == -1) { printf("找不到指定的数字\n"); } else { printf("找到了,下标是:%d\n",ret);//找到了,下标是:6 } return 0; } ``` **4、写一个函数,每调用一次这个函数,就会将 num 的值增加1** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include Add(int* p) { //*p++;因为++的优先级高于p (*p)++; } int main() { int num = 0; Add(&num); printf("%d\n",num);//1 Add(&num); printf("%d\n", num);//2 Add(&num); printf("%d\n", num);//3 return 0; } ``` **5、链式访问** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include //打印printf的函数 #include //链式访问 int main() { int len = 0; len = strlen("abc"); printf("第一种写法:%d\n",len); printf("第二种写法:%d\n",strlen("abc")); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { printf("%d", printf("%d", printf("%d", 43)));//4321 return 0; } ``` **6、函数声明 调用在函数之前** ```c 代码1: //未声明 #define _CRT_SECURE_NO_WARNINGS 1 #include int Add(int x,int y) { int z = x + y; return z; } int main() { int a = 10; int b = 20; int sum = 0; sum = Add(a, b); printf("%d\n",sum); return 0; } 代码2: //声明 #define _CRT_SECURE_NO_WARNINGS 1 #include //函数声明 //Add(int x,int y); Add(int x, int y);//x和y可以省略掉 int main() { int a = 10; int b = 20; int sum = 0; //函数定义 sum = Add(a, b); printf("%d\n",sum); return 0; } //函数定义 在后面 int Add(int x, int y) { int z = x + y; return z; } ``` 公司的常规操作: ![](images\c00041.png) ```c //test.c #define _CRT_SECURE_NO_WARNINGS 1 #include //引用库里面的头文件用<> //以后要使用加法函数 #include "add.h"//引用自己写的头文件用"" //函数实现 int main() { int a = 10; int b = 20; int sum = 0; //函数定义 sum = Add(a, b); printf("%d\n", sum); return 0; } //注意:add.h 和 add.c构成加法模块 //add.h #define _CRT_SECURE_NO_WARNINGS 1 //函数声明 int Add(int x, int y); //add.c #define _CRT_SECURE_NO_WARNINGS 1 //函数定义 在后面 int Add(int x, int y) { int z = x + y; return z; } ``` ![](images\c00042.png) ## 4、函数递归 递归存在限制条件,当满足这个条件的时候,递归便不再继续。 每次递归调用之后越来越接近这个限制条件。 递归不可以无线递归下去,递归层次越深,会出现栈溢出的现象。 一个函数自己调用自己。 史上最简单的递归。 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { printf("lukewei\n");//死循环,不断的去调用 //递归在走的(调用)过程中会出现写问题,到最后半路就退出循环了 //因为栈溢出了 stack overflow栈溢出(递归常见的错误) main();//main函数自己调用自己 return 0; } //函数调用自己:都是会向内存申请空间 内存分区 ``` **接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4** ```c #define _CRT_SECURE_NO_WARNINGS 16 #include void print(int n) { if (n > 9)//满足条件递归继续,不满足条件,递归停止 { print(n / 10);//越来越接近这个条件 } printf("%d ", n % 10); } int main() { int num = 1234; scanf("%d\n", &num); print(num); return 0; } ``` **求字符串的长度** ```c 方法一:别人已经写好的 #define _CRT_SECURE_NO_WARNINGS 1 #include #include int main() { char arr[] = "bit";//'b''i''t' '\0'属于字符串的结束标志,但不属于内容 int len = strlen(arr);//求字符串长度 printf("%d\n",len);//3 return 0; } 方法二:创建临时变量 #define _CRT_SECURE_NO_WARNINGS 1 #include int my_strlen(char* str)//数组在传参的时候只会把第一元素的地址传递过去 { //str存放的是b的地址 //计算字符串的长度 int count = 0;//计数器初始化 while(*str != '\0') { count++; str++;//地址+1,向后走一位 } return count; } int main() { char arr[] = "bit";//'b''i''t' '\0'属于字符串的结束标志,但不属于内容 int len = my_strlen(arr);//求字符串长度 printf("len = %d\n",len);//len = 3 return 0; } 方法三:不创建临时变量 #define _CRT_SECURE_NO_WARNINGS 1 #include int my_strlen(char* str) { //递归思想-把大事化小 //my_strlen("bit"); //1+my_strlen("it"); //1+1+my_strlen("t"); //1+1+1+my_strlen(""); //1+1+1+0 //3 if (*str != '\0') return 1 + my_strlen(str + 1); else return 0; } int main() { char arr[] = "bit"; int len = my_strlen(arr); printf("len = %d\n",len);//len = 3 return 0; } ``` ![](images\c00045.png) 迭代和循环很像,但又区别于循环。 **求n阶乘** ```c 方法一:循环方式 #define _CRT_SECURE_NO_WARNINGS 1 //循环的方式实现n的阶乘 #include int Fac1(int n)//int是函数的返回类型 { int i = 0; int ret = 1; for (i = 1; i <= n; i++) { ret *= i; } return ret; } int main() { int n = 0; int ret = 0; scanf("%d\n",&n); ret = Fac1(n);//循环的方式 printf("%d\n",ret); return 0; } 方法二:递归 #define _CRT_SECURE_NO_WARNINGS 1 #include int Fac2(int n) { if (n <= 1) return 1; else return n * Fac2(n - 1); } int main() { int n = 0; int ret = 0; scanf("%d",&n); ret = Fac2(n); printf("%d\n",ret); return 0; } ``` ![](images\c00047.png) **求第n个斐波那契数。(不考虑溢出)** 递归存在效率相关的问题 ```c 改进前:第50个数比较慢 //描述第n个斐波那契数的时候 #define _CRT_SECURE_NO_WARNINGS 1 #include int Fib(int n) { if (n <= 2) return 1; else return Fib(n - 2) + Fib(n - 1); } int main() {//TDD-测试驱动开发 int n = 0; int ret = 0; scanf("%d",&n); ret = Fib(n); printf("ret = %d\n",ret); return 0; } //弊端:如果第50个数的时候,需要10多分钟才能计算出来 //48 49 // 47 46 //....所以很慢 测试第三个斐波那契数的计算次数: #define _CRT_SECURE_NO_WARNINGS 1 #include int count = 0; int Fib(int n) { if (n == 3) { count++;//计算count在执行如下步骤中到底计算了多少次 } if (n <= 2) return 1; else return Fib(n - 2) + Fib(n - 1); } int main() { int n = 0; int ret = 0; scanf("%d",&n); ret = Fib(n); printf("ret = %d\n",ret);//ret = 102334155 printf("count = %d\n",count);//count = 39088169 比较慢 return 0; } 改进后:第50个数比较快 #define _CRT_SECURE_NO_WARNINGS 1 #include int Fib(int n) { int a = 1; int b = 1; int c = 1; while (n > 2) { c = a + b; a = b; b = c; n--; } return c; } int main() { int n = 0; int ret = 0; scanf("%d", &n); ret = Fib(n); printf("ret = %d\n", ret); return 0; } ``` 递归研究: ```c #define _CRT_SECURE_NO_WARNINGS 1 void test(int n) {//栈溢出 if (n < 10000) { test(n + 1);//递归 } } int main() { test(1); return 0; } ``` **汉诺塔问题:** 青蛙跳台阶: n个台阶 1次可以跳1个台阶 1次也可以跳两个台阶 这只青蛙要跳到n个台阶,有多少种跳法。《剑指Offer67道笔试题》 ## 5、题目 ``` 1、能把函数处理结果的两个数据返回给主调函数,在下面的方法中不正确的是:(a) a.return 这两个数 错误原因:return只能返回一个数 b.形参用数组 正确原因:数组传递回去是地址 c.形参用两个指针 正确原因:函数内部来改变函数外部 d.用两个全局变量 ``` 2、计算每一个数的每位之和(递归实现) ```c #define _CRT_SECURE_NO_WARNINGS #include //输入:1729 输出:19 //DigitSum(1729) //DigitSum(172)+ 1729%10 //DigitSum(17)+ 172%10 + 1729%10 //DigitSum(1)+ 17%10 + 172%10 + 1729%10 //1+7+2+9 int DigitSum(unsigned int num) { if (num > 9) { return DigitSum(num / 10) + num % 10; } else { return num; } } int main() { unsigned int num = 0;//输入一个非负整数 scanf("%d",&num);//1729 int ret = DigitSum(num); printf("ret = %d\n",ret); return 0; } ``` 3、递归实现n^k^ ```c #define _CRT_SECURE_NO_WARNINGS #include double Pow(int n, int k) { if (k == 0) return 1; else if (k > 0) return n*Pow(n, k - 1); else return (1.0 / (Pow(n, -k))); } int main() { int n = 0; int k = 0; scanf("%d%d",&n,&k); double ret = Pow(n, k); printf("ret = %lf\n",ret); return 0; } ``` # 七、数组 ## 1、创建数组的方式 初始化(值) ```c int main() { int arr1[10];//创建一个数组-存放整型-10个 char arr2[5];//创建一个数组用来存放字符 //错误创建方式 //int n = 5; //char ch[n];//应输入常量表达式 return 0; } ``` ```c #include #include int main() { //初始化该数组 int arr1[10] = {1,2,3};//不完全初始化,剩下的7个元素默认初始化为0 char arr2[5] = {'a','b','c','d','e'};//刚好5个全占满 char arr3[5] = { 'a','b' };//占满两个,其它3个全被'\0'占满 char arr4[5] = { 'a','b' ,98};//放了a b b 进去,因为b的ASCII码值是98 //这就是差别 char arr5[5] = "ab";//放3个元素进去arr(包含\0),另外两个才被初始化为0 //不指定数组大小,会根据数组的大小进行确定数组大小 char arr6[] = "abcdef";//放置了7个元素在里面 //sizeof计算arr6所占空间的大小 //7个元素-char char中每一个的大小是1 7*1=7 //strlen是求字符串的长度,找到'\0'就结束了,'\0'不占用内容的。 printf("%d\n",sizeof(arr6));//7 printf("%d\n", strlen(arr6));//6 return 0; } //strlen 和sizeof 没有什么关联 //strlen 是1求字符串长度的-只能针对字符串求长度 - 库函数 - 使用的时候得引入头文件 //sizeof计算变量、数组、类型的大小-单位是字节 - 操作符 - 使用的时候不需要引入头文件 例题: #define _CRT_SECURE_NO_WARNINGS #include int main() { char str[] = "hello bit"; printf("%d %d\n",sizeof(str),strlen(str));// 10 9 return 0; } ``` ```c #include #include int main() { char arr1[] = "abc"; char arr2[] = { 'a','b','c' }; printf("%d",sizeof(arr1));//4 printf("%d", sizeof(arr2));//3 printf("%d", strlen(arr1));//3 printf("%d", strlen(arr2));//随机值15 return 0; } ``` ## 2、一维数组的使用 [ ]下标引用操作符 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { char arr[] = "abcdef"; //[a] [b] [c] [d] [e] [f] printf("%c\n", arr[3]);//访问该数组里面的d元素 int i = 0; int len = strlen(arr); //for (i = 0; i < 6; i++)//方法一 //for(i = 0;i < (int)strlen(arr);i++)//方法二:两种方法都可以使用 //可以进行强制类型转换-strlen默认返回的是无符号整型的一个数值 for(i = 0;i < len;i++)//方法三 { //int在这里叫做有符号的整型 printf("%c ",arr[i]);//a b c d e f } return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,0 }; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 0; i < sz; i++) { printf("%d ",arr[i]); } return 0; } ``` ## 3、一维数组在内存中的存储 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,0 }; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 1; i < sz; i++) { printf("&arr[%d] = %p\n", i,&arr[i]);//%p即使print指针的意思 } return 0; } ``` ## 4、 二维数组的创建和初始化 **二维数组的创建** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[3][4];//表示三行四列的二维数组 char ch[5][6];//第一个[]表示行,第二个[]表示列 return 0; } ``` **二维数组的初始化** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[3][4] = {1,2,3,4,5}; int arr1[3][4] = { {1,2,3},{4,5},{6} }; char ch[5][6]; //int arr2[][] = { 1,2,3,4,5,6,7 }; 直接报错 int arr3[][3] = { {1,2,3},{4,5},{6} };//行可以省略,列省略就出现问题啦 return 0; } ``` ``` #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[3][4] = { {1,2,3},{4,5} }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ",arr[i][j]);//5 //1 2 3 0 //4 5 0 0 //0 0 0 0 } printf("\n"); } return 0; } ``` ## 5、二维数组在数组中怎么存储 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[3][4] = { {1,2,3},{4,5} }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("&arr[%d][%d] = %p ",i,j,&arr[i][j]); } printf("\n"); } return 0; } ``` ![](images\c00050.png) ## 6、冒泡排序排列一个整型数组 ![](images\c00051.png) ```c 改进前: #define _CRT_SECURE_NO_WARNINGS 1 #include //数组传参的时候不会很傻的,再次开辟那么大的空间来接收 //实际上传递过来的实际上是首元素地址&arr[0] void bubble_sort(int arr[],int sz)//因为不需要返回什么,直接输出结果就好了,所以这里使用void { int i = 0; //指针的大小是4 int类型大小也是4 //所以建议在外面写好,直接拿进来就可以使用 //int sz = sizeof(arr) / sizeof(arr[0]);//4/4=1 for (i = 0; i < sz - 1; i++) { //每一趟冒泡排序 int j = 0; for (j = 0;j < sz - 1 -i;j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { int arr[] = {9,8,7,6,5,4,3,2,1,0}; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); //对arr进行排序,排成一个升序 //arr是数组,我们对数组arr进行传参 bubble_sort(arr,sz);//冒泡排序函数 for (i = 0; i <= sz; i++) { printf("%d ", arr[i]); } return 0; } 改进后: #define _CRT_SECURE_NO_WARNINGS 1 #include void bubble_sort(int arr[],int sz) { int i = 0; for (i = 0; i < sz - 1; i++) { int flag = 1;//假设这一趟要排序的数据已经有序了 int j = 0; for (j = 0;j < sz - 1 -i;j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; flag = 0;//本趟排列的数据其实不完全有序 } } if (flag == 1) { break; } } } int main() { int arr[] = {1,2,3,4,5,6,7,8,9};//有序了就不需要在比较了 int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr,sz); for (i = 0; i <= sz; i++) { printf("%d ", arr[i]); } return 0; } ``` ![](images\c00052.png) ``` break语句只用于for和switch,在if语句中不能贸然使用,因为if不是循环语句,所以不能用break来结束。 ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 int main() { if (1) //break;//报错 非法break return 0; } //break只能用于for和switch语句,但是嵌套的时候可以使用 ``` ## 7、数组名 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7 }; printf("%p\n",arr);//数组名 0069F744 printf("%p\n", &arr[0]);//首元素地址 0069F744 printf("%d\n", *arr);//1 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7 }; int sz = sizeof(arr) / sizeof(arr[0]); //第一点: //sizeof(数组名) -数组名表示整个数组 //sizeof(数组名) -计算的是整个数组的大小,单位是字节 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,6,7 }; printf("%p\n",arr);//0076FC3C printf("%p\n", arr+1);//007DFD24 printf("%p\n", &arr[0]);//0076FC3C printf("%p\n", &arr[0]+1);//007DFD24 printf("%p\n", &arr);//0076FC3C -取出整个数组的地址,用第一个元素代替 printf("%p\n", &arr+1);//007DFD3C 拿到数组大小是28=7*4 // 第二点: //&arr -取地址数组名的时候,数组名代表整个数组 //&数组名 ,取出的是整个数组的地址 return 0; } ``` ![](images\c00053.png) ## 8、题目 ```c 1、定义如下数组 char acX[] = "abcdefg"; char acY[] = {'a','b','c','d','e','f','g'}; 数组acX和数组acY不等价,长度acX为8个元素,acY为7个元素。 2、阅读数组:打印10个0 #define _CRT_SECURE_NO_WARNINGS #include void Init(int arr[],int sz)//无需返回值,就把类型定义为void //该函数对数组初始化为全0,无需返回任何值 { int i = 0; for (i = 0; i < sz; i++) { arr[i] = 0; } } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]);//0 0 0 0 0 0 0 0 0 0 } } int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的总大小 //函数内部没有办法计算到外部数组的个数 //因为函数内部不能使用sizeof来求外部数组的总大小 Init(arr,sz);//把数组初始化为全0 print(arr,sz); return 0; } 3、阅读翻转代码 #define _CRT_SECURE_NO_WARNINGS #include void Reverse(int arr[],int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ",arr[i]); } printf("\n"); int left = 0; int right = sz - 1; while (left < right) { int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ",arr[i]); } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); Reverse(arr, sz); print(arr,sz); return 0; } 4、翻转代码案例 //字符串逆序(递归实现),不是单纯的打印反过来,而是改变原来的数组让其逆序 #define _CRT_SECURE_NO_WARNINGS #include #include //void reverse_string(char* arr) void reverse_string(char arr[])//alternative { int left = 0; int right = strlen(arr) - 1; while (left < right) { int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } } int main() { char arr[] = "abcdef";//数组里面放着a b c d e f\0 reverse_string(arr); printf("%s\n",arr);//fedcba return 0; } 5、翻转代码案例 计数器 //字符串逆序(递归实现),不是单纯的打印反过来,而是改变原来的数组让其逆序 #define _CRT_SECURE_NO_WARNINGS #include #include int my_strlen(char* str) { //方法一:计数器 这道题目用 //方法二:递归方法 //方法三:指针-指针 int count = 0; while (*str != '\0') { count++; str++; } return count; } //void reverse_string(char* arr) void reverse_string(char arr[])//alternative { int left = 0; int right = my_strlen(arr) - 1; while (left < right) { int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } } int main() { char arr[] = "abcdef"; reverse_string(arr); printf("%s\n",arr); return 0; } 6、翻转代码案例 递归方法 //字符串逆序(递归实现),不是单纯的打印反过来,而是改变原来的数组让其逆序 #define _CRT_SECURE_NO_WARNINGS #include #include int my_strlen(char* str) { //方法一:计数器 这道题目用 //方法二:递归方法 //方法三:指针-指针 int count = 0; while (*str != '\0') { count++; str++; } return count; } //void reverse_string(char* arr) void reverse_string(char arr[])//alternative { //递归思想:大事化小 char tmp = arr[0]; int len = my_strlen(arr); arr[0] = arr[len - 1]; arr[len - 1] = '\0'; if (my_strlen(arr + 1) >= 2) reverse_string(arr + 1); arr[len - 1] = tmp; } int main() { char arr[] = "abcdef"; reverse_string(arr); printf("%s\n",arr); return 0; } 7、交换两个数组的内容 #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr1[] = { 1,3,5,7,9 }; int arr2[] = { 2,4,6,8,0 }; /*极其错误的代码 int tmp[5] = { 0 };//因为数组名代表的是首元素的地址 临时数组浪费空间,还不知道多大 //直接创建一个临时变量就行 tmp = arr1;//这就相当于5 = 3 是不能存放进去的 arr1 = arr2; arr2 = tmp; */ int tmp = 0; int i = 0; int sz = sizeof(arr1) / sizeof(arr1[0]); for (i = 0; i < sz; i++) { tmp = arr1[i]; arr1[i] = arr2[i]; arr2[i] = tmp; } for (i = 0; i < sz; i++) { printf("%d ", arr1[i]); } for(i = 0;i int main() { //移(二进制) 位操作符 // << 左移 <<右移 int a = 1;//整型1占4个字节=36bit位 //00000000000000000000000000000001 int b = a << 1;//00000000000000000000000000000010 1*2^1+0*2^0=2 int c = a << 2;//00000000000000000000000000000100 1*2^2+0*2^1+0*2^0=4 printf("b = %d\n", b);//2 printf("c = %d\n", c);//4 printf("a = %d\n", a);//1 a是不变的 //相当于 b = a + 1; b+1但是a是不会改变的 return 0; } ``` 左边丢弃,右边通常补0。 例如:向左移动两位:最左边多出两个0,最右边补充两个0。 ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { //16的二进制位是10000=2^4+0+0+0+0=16 int整型:占用32个bit位或4个字节 int a = 16;//补齐就是00000000 0000000000000000 00010000 32个bit位 //>> --右移操作符:移动的是二进制位 int b = a >> 1;//二进制序列为 00000000 0000000000000000 00001000 printf("b = %d\n",b);//b = 8 因为 1 * 2^3次方 //推理:16右移两位就变成4啦 因为1 * 2^2次方 //总结:说明右移一位有除以2的效果 //举例验证 int c = -1; int d = b >> 1; printf("d = %d\n",c);//d = 1 //举例验证说明:该右移是算术右移 //说明当前编译器下采用的是算术移位,而不是逻辑移位 //总结:多数情况下都是算术右移 return 0; } ``` ![](images\c00055.png) ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = -1;//a的二进制序列(形式) //1(符号位:最高位代表负数)0000000 00000000 00000000 00000001(最后的1表示实际的数字是1) -原码 //1 1111111 11111111 11111110 -反码:符号位不变,其他位按位取反 //1 1111111 11111111 11111111 -补码:反码+1 //由此可见-1在内存中存储的是补码 //可以调试监视窗口中查看 输入取地址&a就行 int b = a >> 1; //一个f是15(15换成二进制序列是1111)其实就是4个1 //1111 //8421 (1*2^3+1*2^2+1*2^1+1*2^0=8+4+2+1=15) //这里有8个f就是32个1 return 0; } //整数的二进制表示有:原码、反码、补码 //对于正整数来说,原码=反码=补码。对于负数有差别。 //存储到内存中的是补码 //所以这里的a在移位的时候,移动的是其补码 ``` ![](images\c00057.png) **左移操作符** ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 5;//5二进制序列是101 //101改写成32位bit位就是00000000 00000000 00000000 00000101 int b = a << 1; printf("%d\n",b);//10 //可见,向左移动一位其实有*2 乘2的效果 return 0; } //左移操作符:左边丢弃,右边补0 ``` ## 3、位(二进制位)操作符 只能作用于整数,不能作用于浮点数。 float和double都是浮点数。 ![](images\c00015.png) ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 3;//000(0) 001(1) 010(2) 011(3) 100(4) 101(5) 110(6) 111(7) 3是011 int b = 5;//101 int c = a & b;//001 int d = a | b;//111 int e = a ^ b;//110 printf("%d\n",c);//1 printf("%d\n",d);//7 printf("%d\n",e);//6 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { //& 按位(二进制位)与-全1得1,有0则0 int a = 3;//011补全是00000000 00000000 00000000 00000011 int b = 5;//101补全是00000000 00000000 00000000 00000101 int c = a & b;//00000000 00000000 00000000 00000001 该二进制序列是1 printf("%d\n",c); return 0; } //如果给一个负数,就要把补码拿出来进行运算 ``` ```c #define _CRT_SECURE_NO_WARNINGS #include //| 按位(二进制位)或 int main() {//有1则1,全1则1 int a = 3;//011补全是00000000 00000000 00000000 00000011 int b = 5;//101补全是00000000 00000000 00000000 00000101 int c = a | b;//00000000 00000000 00000000 00000111 该二进制序列是4+2+1=7 printf("%d\n", c); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include //^ 按位异或 int main() {//对应的二进制位相同则为0,相异为1 int a = 3;//011补全是00000000 00000000 00000000 00000011 int b = 5;//101补全是00000000 00000000 00000000 00000101 int c = a ^ b;//00000000 00000000 00000000 00000110 该二进制序列是110 8421 printf("%d\n", c);//6 return 0; } ``` **变态面试题:交换两个数字,不使用第三个变量。** ```c 方法一:工作中推荐的方式 //使用第三个变量-临时变量 #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 3; int b = 5; int tmp = 0; printf("交换前:a = %d b = %d\n", a, b);//交换前:a = 3 b = 5 tmp = a; a = b; b = tmp; printf("交换后:a = %d b = %d\n",a,b);//交换后:a = 5 b = 3 return 0; } 方法二: //使用加减法 #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 3; int b = 5; printf("交换前:a = %d b = %d\n", a, b);//交换前:a = 3 b = 5 a = a + b;//可能会造成二进制位丢失,导致a里面放的不是二进制位的和 b = a - b;//还原不出来 a = a - b;//溢出导致还原不回来 printf("交换后:a = %d b = %d\n",a,b);//交换后:a = 5 b = 3 return 0; } 方法三:不推荐在工作中使用,执行效率不高,可读性比较差。 //使用加减法 #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 3;//011 int b = 5;//101 //不会产生进位0-很猛 printf("交换前:a = %d b = %d\n", a, b);//交换前:a = 3 b = 5 a = a ^ b;//结果为110(6) 相同为0,相异为1 密码 b = a ^ b;//结果为011(3) 密码和原来的3异或能翻译出原来的5 a = a ^ b;//结果为101(5) 密码和原来的5异或能翻译出原来的3 printf("交换后:a = %d b = %d\n",a,b);//交换后:a = 5 b = 3 return 0; } ``` **求一个整数存储在内存中的二进制中1的个数。** ```c 改进前: #define _CRT_SECURE_NO_WARNINGS #include int main() { int num = 0; int count = 0; scanf("%d",&num);//3 -011 //num的二进制:存储到内存中是补码 //统计补码中1的个数 while (num) { if (num % 2 == 1)//第一次:得到最右边的1 第二次:01 % 2 = 1(得到右边的1) count++;//统计一次 num = num / 2;//把最后(右)一个1去掉,剩下01 就是目前的num 第二次:得到0结束程序 } printf("%d\n",count); return 0; } //原理:123 //123 /* 123 / 10 = 12 去掉3 123 % 10 = 3 得到最后一位 12 / 10 = 1 12 % 10 = 2 */ ``` ![](images\c00058.png) ```c 改进后: #define _CRT_SECURE_NO_WARNINGS #include int main() { //00000000 00000000 00000000 00000011[0](3) //00000000 00000000 00000000 00000001 (1) //00000000 00000000 00000000 00000001[0] int num = 0; int count = 0; scanf("%d",&num);//-1 int i = 0; for (i = 0; i < 32; i++) { if (1 == ((num >> i) & 1)) count++; } printf("%d\n",count); return 0; } ``` ## 4、赋值操作符 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { //复合赋值符 int a = 10;//初始化 a = 20;//=赋值 ==判断是否相等 a = a + 10;//等价下式 a += 10;//等价上式 a = a - 20; a -= 20; a = a & 2; a &= 2; return 0; } ``` **复合赋值符** ```c #define _CRT_SECURE_NO_WARNINGS int main() { int a = 10; a = a + 2; //可以写成复合赋值符 a += 2; a = a >> 1;//可以写成 a >>= 1; return 0; } ``` ## 5、单目操作符 单目操作符:只有一个操作数。 C语言中我们表示真假,0表示假的,1表示真的。 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10; int b = 0; printf("%d\n", a);//10 printf("%d\n", !a);//逻辑反操作 0 printf("%d\n", !b);//1 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 10; int b = 0; int c = 0; printf("%d\n",!a);//0 printf("%d\n", !b);//1 1不一定是真,例如5 8 9都是真 if (a) { printf("到底哈\n");//到底哈 } if (!c) { printf("likewei\n");//likewei } return 0; } ``` **正负:** ``` #define _CRT_SECURE_NO_WARNINGS 1 int main() { int a = -2; int b = -a; int c = +3;//+号都会省略 return 0; } ``` **sizeof:计算的是变量、类型所占空间的大小,单位是字节** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10; char c = 'r';//一个字符,占用一个字节 char* p = &c;//指针大小要么是4个字节(32bit平台),要么是8个字节(64bit平台) int arr[10] = { 0 };//10个元素,每个元素都是一个整型,占用40个字节 printf("%d\n", a);//10 //这里sizeof用来计算a的大小 printf("%d\n", sizeof(a));//4个字节(因为是整型) 等价与下面的 printf("%d\n", sizeof(int));//4个字节 等价与上面的 printf("%d\n", sizeof a);//计算变量大小时候可以省略括号 //printf("%d\n", sizeof int);//计算变量类型时候不可以省略括号 //sizeof 计算变量所占内存空间的大小-单位是字节 printf("c = %d\n",sizeof(c));//c = 1 printf("c = %d\n", sizeof (char));//c = 1 printf("p = %d\n", sizeof(p));//p = 4 printf("p = %d\n", sizeof(char*));//p = 4 printf("arr = %d\n", sizeof(arr));//arr = 40 //数组类型很特殊: int[5]、int[10] 、char[5]他们都不一样 printf("arr = %d\n", sizeof(int[10]));//arr = 40 printf("arr = %d\n", sizeof(int[5]));//arr = 20 return 0; } ``` ```c 计算数组大小: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int sz = 0; int arr[10] = { 0 };//创建10个整型元素的数组 //10 * sizeof(int) = 40 printf("%d\n", sizeof arr);//40 printf("%d\n", sizeof (int));//4 //计算数组元素个数 = 数组总大小 / 每个元素大小 printf("%d\n", sizeof arr / 4);//10 sz = sizeof(arr) / sizeof(arr[0]); printf("sz = %d\n", sz);//10 return 0; } ``` ```c sizeof操作符: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10; int arr[] = { 1,2,3,4,5,6 }; printf("%d\n", sizeof(a));//4 可见sizeof是操作符,不是函数 printf("%d\n", sizeof a);//4 函数后面的括号是不能省略的 printf("%d\n", sizeof(int));//4 printf("%d\n", sizeof(arr));//24 计算数组大小 //数组为啥不能打印int呢?因为数组的类型不是int,只是概括性的 //数组里面的元素可以随机就不一定是int类型 //理解数组类型:int[6] 6是数组的一个类型 printf("%d\n", sizeof(arr[0]));//4个字节 printf("%d\n", sizeof(arr) / sizeof(arr[0]));//6个整型元素 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { short s = 0;//短整型依然是整型 int a = 10; //sizeof算的是所占空间的大小 无论之前int是4个字节,拿到short里面就是两个字节 printf("%d\n", sizeof(s = a + 5));//2 s是一个short,占用两个字节 printf("%d\n", s);//0 s是不会发生变化的, return 0; } ``` **~ 对一个数的二进制按位取反** ```c //按位取反示例 ~按位(二进制位)取反 //1010 //0101 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { //对于0来说,是一个正数,正数的原码、反码、补码相同-内存中存储的是二进制序列的补码 int a = 0;//0的二进制表示形式00000000000000000000000000000000(二进制序列) //按位取反后还是补码 int b = ~a;//11111111111111111111111111111111(二进制序列) b是有符号的整型 //b的第一位,也是最高位是符号位 为1则为负数 为0则为正数 printf("%d\n", b);//-1 return 0; } //原码:符号位不变,其他位按位取反得到反码, //原码:10000000000000000000000000000001(-1) //反码:11111111111111111111111111111110 反码+1得到补码 //补码:11111111111111111111111111111111 //任何数字在内存中存的都是补码,反码只是计算的一个中间状态 ``` ```c //按位取法实际例子 //1010 按位取反前 0变1,1变0 //0101 按位取反后 #define _CRT_SECURE_NO_WARNINGS 2 #include int main() {//~代表的是按(二进制)位取反 int a = 0;//4个字节是32个bit位 00000000000000000000000000000000 0的二进制序列 int b = ~a;//b是有符号的整型,最高位(第一位)是符号位 存放的是补码 11111111111111111111111111111111 printf("%d",b);//-1 使用时打印原码 //反码11111111111111111111111111111110 源码10000000000000000000000000000001 //最高位不用管它,直接是-1就行 return 0; } //正数的原码、补码、反码相同 //负数在内存中存储的时候,存储的是二进制的补码,使用的,打印的是该数的源码 -2有32bit位: 源码:10000000000000000000000000000010 最高位为符号位:0代表正数,1代表负数 反码:11111111111111111111111111111101 最高位为符号位:0代表正数,1代表负数 补码:11111111111111111111111111111110 只要是整数,内存中存储的都是二进制的补码 //原码00000000000000000000000000000000 符号位不变,其他按位取反=>反码 //反码01111111111111111111111111111111 加1=>补码 //补码10000000000000000000000000000000 ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 11;//二进制是1011 //写全是00000000 00000000 00000000 00001011 //或数字00000000 00000000 00000000 00000000(0) //目标是00000000 00000000 00000000 00001111(15) a = a | (1 << 2); printf("%d\n",a);//15 return 0; } //000000000 000000000 000000000 000000001(1) //1 << 2得到 //000000000 000000000 000000000 000000100 ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 15;//二进制是1111 //起点是00000000 00000000 00000000 00001111(15) //或数字11111111 11111111 11111111 11111011(0) //目标是00000000 00000000 00000000 00001011(11)或运算 a = a & (~(1 << 2)); printf("a = %d\n",a);//a = 11 return 0; } //第一步:00000000 00000000 00000000 00000001左移两位 //第二步:00000000 00000000 00000000 00000100按位取反得到 //第三步:11111111 11111111 11111111 11111011 ``` **--/++** ```c //前置++ #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int a = 10; int b = ++a;//前置++ 对a而言,先进行a自己的自增+1,然后再把得到的结果赋值给b(先++,再使用) printf("a = %d,b = %d\n",a,b);//a=11 b=11 return 0; } //前置-- #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int a = 10; int b = --a;//前置-- 对a而言,先进行自己的自减,然后再把得到的结果赋值给b(先自减,再使用) printf("a = %d,b = %d\n", a, b);//a=9 b=9 return 0; } //后置++ #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int a = 10; int b = a++;//对a而言,先把a的值传递给b,然后再进行自己的自增(先使用,再++) printf("%d\n",b);//b=10 a=11 return 0; } //后置-- #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int a = 10; int b = a--;//后置-- 对a而言,先把a的值传递给b,然后再进行自己的自减(先使用,再自减) printf("a = %d,b = %d\n", a, b);//a=9 b=10 return 0; } 总结: #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 10; //printf("%d\n",++a);//前置++,先自增,再使用 11 printf("%d\n",a++);//后置++,先使用,后自增 10 printf("%d\n",a);//11 } ``` ```c 面试题1: #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++ && ++b && d++;//&&左边算出是0,右边就不算啦 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//1 2 3 4 return 0; } 面试题2: #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int i = 0, a = 1, b = 2, c = 3, d = 4; i = a++ || ++b || d++;//||左边算出是1,右边就不算啦 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//2 2 3 4 return 0; } ``` **强制类型转换** 一般不建议推荐在代码里进行转换,如果不转换的尽量不要转换 ```c #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { //int a = 3.14;//如果不加括号可能会丢失数据 会报警告 int a = (int)3.14;//double转换为int类型 printf("%d\n",a);//3 } ``` ## 6、双目操作符 a + b 有两个操作数 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10; int b = 20; a + b;//双目操作符:左右两边有两个操作数 return 0; } ``` ## 7、逻辑操作符 ```c 0表示假 非0表示真 //&& 逻辑与 并且 #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int a = 5; int b = 3; int c = 0; int d = a && b;//全真为真 int e = a && c;//有假即假 int f = a || b;//有真即真 int g = b || c;//有真即真,全假为假,全真为真 printf("逻辑与:d = %d,e = %d\n",d,e);//d=1 e=0 printf("逻辑或:f = %d,g = %d\n",f,g);//f=1 g=1 return 0; } ``` ## 8、条件操作符(三目操作符) 求两数的较大值 ```c //if...else...来操作 #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int num1 = 32; int num2 = 160; if (num1 > num2) { printf("较大值是: %d\n",num1); } else { printf("较大值是: %d\n", num2); } return 0; } ``` ```c //函数来操作 #define _CRT_SECURE_NO_WARNINGS 2 #include #include //Max() 5 代表的是一个函数 通过int x, int y来接收2 int Max(int x, int y) {//函数体 if (x > y) return x;//6 用int来呼应 else return y;//6 用int来呼应 } int main() { int num1 = 1577;//1 int num2 = 20;//1 int max = 0;//4 想让最终得到的结果放到max这个变量值里面 max = Max(num1, num2);//2 用Max来求,求出来告诉我 printf("最大值是:%d\n",max);//打印max return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 2 #include int main() { int a = 1000; int b = 20; int max = 0; max = (a > b ? a : b);//1000赋值给max printf("最大值:%d\n", max);//20 return 0; } ``` ```c 例题1: #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 0; int b = 0; //if (a > 5) // b = 3; //else // b = -3; //用三目表达式写就是 a = a > 5?(b = 3) : (b = -3); printf("%d",a);//-3 return 0; } 例题2: #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 10; int b = 20; int max = 0; //if (a > b) // max = a; //else // max = b; //三目操作符形式 max = (a > b ? 10 : 20); printf("%d\n",max); return 0; } ``` **9、下标引用、函数调用和结构成员** ```c //[ ] //( ) //.和->结构体会讲到 #define _CRT_SECURE_NO_WARNINGS 2 int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int arr[10] = { 0 }; int a = 11; int b = 22; arr[4];//[]就是下标引用操作符 int sum = Add(a, b);//()就是函数调用操作符 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS int main() { int arr[10] = { 0 }; arr[4] = 0;//[]有 4 和 arr 两个操作数 1 + 2;//+号有 1 和 2 两个操作数 //总结:只要是操错符就有操作数 return 0; } ``` **函数调用操作符** ```c #define _CRT_SECURE_NO_WARNINGS #include int get_max(int x, int y)//() 这里的()是定义函数的语法规则 { return x > y ? x : y; } int main() { int a = 80; int b = 20; int max = get_max(a,b);//() 这里的()就是函数调用操作符 //()操作数是3个(最少有一个,传空参的时候) get_max、a、b printf("%d和%d中较大值是%d",a,b,max); return 0; } ``` ## 9、解引用操作符\取地址操作符 ```c #define _CRT_SECURE_NO_WARNINGS int main() { int a = 10; //int b = 3.14;//这样转换可能造成数据丢失 int b = (int)3.14;//强制类型转换 //int c = int(3.14);//错误写法 int* p = &a;//取地址操作符-一般和指针配合起来使用 int* 就是p的类型 *p = 20;//*p就是a *就是解引用操作符 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include void test1(int arr[]) { printf("%d\n", sizeof(arr));//40 } void test2(char ch[]) { printf("%d\n", sizeof(ch));//10 } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%d\n", sizeof(arr));//4 printf("%d\n", sizeof(ch));//4 test1(arr); test2(ch); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include //描述一个学生对象 //我们所指的int float是一种类型 //创建一个结构体类型 struct Stu struct Stu//struct Stu也是一种类型 { //描述这个对象所需要的成员变量:描述相关的成员信息 char name[20]; int age; char id[20]; }; int main() { int a = 10;//类型是用来创建变量的 //使用struct Stu这个类型创建一个学生对象s1,并初始化该对象 struct Stu s1 = {"张三",20,"201902020132"}; printf("%s\n",s1.name);//张三 printf("%d\n", s1.age);//20 printf("%s\n", s1.id);//201902020132 //结构体变量.成员名 //啰嗦代码-不简便 struct Stu* ps = &s1; printf("%s\n",(*ps).name);//张三 printf("%d\n", (*ps).age);//20 printf("%s\n", (*ps).id);//201902020132 //简结代码 printf("%s\n",ps->name);//和上面没有任何区别 张三 printf("%d\n",ps->age);//20 printf("%s\n",ps->id);//201902020132 //->结构体指针操作符 //结构体指针->成员名 return 0; } ``` ## 10、整型提升 ```c //隐式类型转换-有转换动作-偷偷发生(隐式发生) #define _CRT_SECURE_NO_WARNINGS #include int main() { //00000000 00000000 00000000 00000011-a char a = 3;//遗憾的是a只能放1个字节,只能放8个bit位 00000011-a //00000000 00000000 00000000 01111111-b char b = 127;//01111111-b //00000011-b+ 01111111-a= //整形提升 //00000000 00000000 00000000 00000011-b //00000000 00000000 00000000 01111111-a //00000000 00000000 00000000 10000010-相加的结果 char c = a + b; //10000010-c 前面补和符号位一致的 //提升后:11111111 11111111 11111111 10000010-c 补码 //11111111 11111111 11111111 10000001-反码 //10000000 00000000 00000000 01111110-原码 //-126 printf("%d\n",c);//-126 %d-打印整型-整型提升 return 0; } ``` ## 11、题目 ```c 1、表达式求值先看是否存在整型提升和算术算术转换,再进行计算。 2、表达式真正计算的时候先看相邻的优先级决定先算谁。 3、相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序。 4、阅读代码 ``` ![](images\c00067.png) ```c 5、阅读代码 #define _CRT_SECURE_NO_WARNINGS #include int main() { int a, b, c; a = 5; c = ++a; b = ++c, c++, ++a, a++;//注意:大坑 这里连续4个逗号表达式 b += a++ + c; printf("a = %d b = %d c = %d\n",a,b,c);//a = 9 b = 23 c = 8 return 0; } ``` 6、统计一个二进制中1的个数 ```c #define _CRT_SECURE_NO_WARNINGS #include #include //system的头文件 //求二进制中1的个数:是求补码 //数字在内存中存储是用补码-一个数的补码的二级制序列有多少个1 int count_bit_one(int n)//返回的个数是一个整数,所以用int来定义数据类型 { int count = 0; while (n) { if (n % 2 == 1) { count++; } n = n / 2; } return count; } int main() { int a = 0; scanf("%d",&a); //写一个函数求a的二进制(补码)表示中有几个1 int count = count_bit_one(a); printf("count = %d\n",count); system("pause");//system库函数-执行系统命令-pause(暂停) //每写一个代码都要加-让其代码停下来 return 0; } //1231 %10 /10得到十进制的每一位 //00000000 00000000 00000000 00001101(13) //该代码的弊端:负数无法表示 得到结果为0可以按程序推理出来 //原码:10000000 00000000 00000000 00000001(-1) 符号位不变,其他位按位取反 //反码:11111111 11111111 11111111 11111110 反码+1 //补码:11111111 11111111 11111111 11111111 ``` 7、两个数的二进制中有多少个1? ```c #define _CRT_SECURE_NO_WARNINGS #include int get_diff_bit(int m,int n) { //计算m和n有多少个1不同 int tmp = m ^ n; //统计tmp里面有多少个1 int count = 0; while (tmp) { tmp = tmp & (tmp - 1);//让tmp每次二进制位少1 count++; } return count; } int main() { int m = 0; int n = 0; scanf("%d%d",&m,&n); int count = get_diff_bit(m, n); printf("count = %d\n",count); return 0; } ``` 8、打印一个数的二进制序列的奇数位和偶数位? ```c #define _CRT_SECURE_NO_WARNINGS #include void print(int m) { //00000000 00000000 00000000 00001010 int i = 0; printf("奇数位\n"); for (i = 30; i >= 0; i -= 2) { printf("%d ", (m >> i) & 1); } printf("\n"); printf("偶数位\n"); for (i = 31; i >= 0; i -= 2) { printf("%d ", (m >> i) & 1); } } int main() { int m = 0; scanf("%d",&m); print(m);//打印奇数列和偶数列 return 0; } ``` # 九、关键字 ## 1、auto ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10;//局部变量,a是在{}内部是自动创建(进{}),自动销毁(出{}) auto int b = 10;//默认省略了auto(因为局部变量都是自动变量),这是原本的局部变量 //extern 引入外部符号 return 0; } ``` ## 2、register ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10;//从内存拿取数据比较慢,放到寄存器中就比较快 register int a = 10;//建议把a定义成一个寄存器变量 //计算机中计算器数量是有限的,不能滥用的。主要配合编译器来使用 return 0; } ``` ## 3、有符号数 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10; a = -2;//int定义的变量是有符号的 signed int b = -5;//int前面是有符号的,通常省略。signed有符号位。 unsigned int num = 0;//无符号位,放一个负数进去,得到的依旧是正数。 return 0; } ``` ## 4、struct结构体关键字 union 联合体、共用体 void 无、空 volatile 用的比较少,比较难 ## 5、typedef 类型定义 ```c //也叫类型重定义 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { typedef unsigned int u_int;//起别名 unsigned int num = 20;//无符号的整型num u_int num1 = 20;//u_int和unsigned int变量类型一模一样 return 0; } ``` ## 6、static 修饰局部变量,当修饰局部变量的时候,局部变量的生命周期延长了。 **static修饰局部变量:** ```c 改进前: #define _CRT_SECURE_NO_WARNINGS 1 #include void test() {//进入函数,局部变量的生命周期开始 int a = 1; a++;//a = 2 printf("a = %d\n", a);//a = 2 输出5个 //出去函数,局部变量的生命周期结束销毁,空间还原给系统 } int main() { int i = 0; while (i < 5) { test(); i++; } return 0; } 改进后:会保存上一次的变量 static修饰局部变量 #define _CRT_SECURE_NO_WARNINGS 1 #include void test() { static int a = 1; a++;//a = 2 printf("a = %d\n", a);//a = 2 a = 3 a=4 a=5 a=6 } int main() { int i = 0; while (i < 5) { test(); i++; } return 0; } ``` **static修饰全局变量:** 让静态的全局变量只能在自己所在的源文件内部使用,出了源文件就没法再使用了 ```c 改进前可以正常使用: //add.c #define _CRT_SECURE_NO_WARNINGS 1 int g_val = 2023;//定义全局变量,作用域很大 //test.c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { //extern关键字声明外部符号 extern int g_val; printf("%d\n", g_val);//2023 return 0; } 改进后报错:static改变了作用域 一个无法解析的外部命令 #define _CRT_SECURE_NO_WARNINGS 1 static int g_val = 2023;//定义全局变量,作用域很大 ``` **static修饰函数:** 也是改变了函数的作用域-不正确 改变了函数的链接属性,外部链接属性变成内部链接属性。 外部链接属性:普通函数都有。 ```c 改进前: //add.c #define _CRT_SECURE_NO_WARNINGS 1 //定义一个函数 int Add(int x, int y) { int z = x + y; return z; } //test.c #define _CRT_SECURE_NO_WARNINGS 1 #include extern int Add(int, int);//声明外部函数 extern 函数的返回类型 函数名(第一个参数类型,第二个参数类型) int main() { int a = 10; int b = 20; int sum = Add(a, b); printf("%d\n",sum); return 0; } 改进后:无法解析的外部命令 #define _CRT_SECURE_NO_WARNINGS 1 //定义一个函数 static int Add(int x, int y) { int z = x + y; return z; } ``` ```c 作业:计算题 #define _CRT_SECURE_NO_WARNINGS 1 #include int sum(int a) { int c = 0; static int b = 3; c += 1; b += 2;//b=5 return(a + b + c);//2+5+1=8 } int main() { int i; int a = 2; for (i = 0; i < 5; i++) { printf("%d\n", sum(a));//8 10 12 14 16 } return 0; } ``` ## 7、#define **#define定义标识符常量的方法** ```c #define _CRT_SECURE_NO_WARNINGS 1 #include #define MAX 100;//定义MAX等于100 int main() { int a = 100; int b = MAX; return 0; } ``` **#define定义宏** 宏就是在标识符的基础上加了一个参数,宏是带参数的 ```c //函数方式取较大值 #define _CRT_SECURE_NO_WARNINGS 1 #include int Max(int x, int y) { if (x > y) { return x; } else { return y; } } int main() { int a = 10; int b = 20; int max = Max(a, b); printf("max= %d\n", max); return 0; } //宏的方式取较大值 #define _CRT_SECURE_NO_WARNINGS 1 #include #define MAX(X,Y) (X>Y ? X : Y)//宏的定义 int main() { int a = 10; int b = 20; int max = MAX(a, b); max = MAX(a, b);//宏的方式 printf("max= %d\n", max); return 0; } ``` ``` 1、用在switch语言的关键字不包含哪个?a a. continue b.break c.default d.if 2、define不是一个关键字,是一个预处理指令。 ``` # 十、指针 ```c 1. 字符指针 #define _CRT_SECURE_NO_WARNINGS #include int main() { char ch = 'w';//定义一个字符变量 char* pc = &ch;//定义一个字符指针变量 char arr[] = "abcdef";//定义数组并初始化为字符串 char* pd = &arr;//arr数组名=首元素a的地址 char* p = "abcdef";//a的地址赋值给p //直接放不下,因为"abcdef"常量字符串里面包含\0是7个字符,指针只有4个字节 //常量字符串里面的内容是不可以被修改的 //*p = 'k'; 代码会崩溃 const char* n = "likewei";//const修饰的是*n //const可以让n里面的内容无法被修改 printf("%s\n", arr);//abcdef printf("%s\n", pd);//abcdef printf("%c\n",*p);// a %c只是打印一个字符 printf("%s\n", p);//abcdef return 0; } #define _CRT_SECURE_NO_WARNINGS int main() { //字符指针 char ch = 'w'; char* p = &ch; //下面只要找到a的地址就能找到字符串的地址 char* p2 = "abcdef";//首字符a的地址传给p2 指针变量(4个字节)是存不下该常量字符串的 //常变量字符不允许被修改,加一个const就保持让其不被修改 const char* p3 = "abcdef";//最科学最合理的写法 return 0; } 面试题: 2. 数组指针:指向数组的指针 3. 指针数组 4. 数组传参和指针传参 5. 函数指针 6. 函数指针数组 7. 指向函数指针数组的指针 8. 回调函数 9. 指针和数组面试题的解析 1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。 指针变量里面存放的就是一个地址,使用指针变量就是使用里面保存的地址。指针等同地址。 2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。 #define _CRT_SECURE_NO_WARNINGS #include //传来一个地址(实质就是指针),定义一个整型数组来接收 //整型数组每个元素大小都是4个字节 void test(int arr[]) { printf("%d\n",sizeof(arr)/sizeof(arr[0]));//4/4=1 8/4=2 } int main() { int arr[10] = { 0 }; test(arr);//传递数组地址-首元素地址 return 0; } 显示如下图所示: ``` ![](images\c00081.png) ``` 3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。 ①下面两种结果步长是不一样的: 一个整型指针+1(拓展到整数)或者-1(拓展到整数)的步长、一个字符指针+1或者-1的步长 ②一个指针解引用 一个整型指针解引用访问4个字节,一个字符指针解引用访问1个字节。 4. 指针的运算。 指针+或-整数、指针的关系运算、指针-指针相关运算方式。 ``` 指针是变量,用来存放地址。 指针变量中存在的有效地址可以唯一指向内存中的一块区域。 在32bit操作系统下,int整型类型占用**4**个字节,指针占用**4**个字节,操作系统可以使用的最大内存访问空间是**2 ^32^**个字节(2 ^32^个地址对应2 ^32^字节)。 ```c 指针类型的意义:①指针类型决定了指针解引用操作符能访问几个字节:char*p; *p 访问了一个字节。 int*p; *p能够访问4个字节。 ②指针类型决定了指针+1,-1 ,加的或者减的是几个字节;例如:char*p; p+1,跳过一个字符; int* p; p+1,跳过一个整型-4个字节 #define _CRT_SECURE_NO_WARNINGS int main() { int a = 0x11223344; //int* p = &a; //*p = 0; //对照 char* p = &a; *p = 0; return 0; } ``` 在64bit操作系统下,int整型类型占用**4**个字节,指针占用**8**个字节,操作系统可以使用的最大内存访问空间是**2^64^**个字节。(2 ^64^个地址对应2 ^64^字节) ![](images\c00020.png) ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10;//4个字节 int* p = &a;//取出a的地址 p就是指针变量,类型为int*,p变量里面存的是a的地址 //有一种变量是用来存放地址的:指针变量 printf("%p\n",&a);//%p是打印地址的 00D7FBE8 十六进制 其实还是一个二进制序列,只是展示的是十六进制 printf("%p\n", p);//007BF84C *p = 20;//接引用操作符,对p进行解引用操作,找到p所指向的对象a printf("%d\n", a);//20 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { char ch = 'w'; char* pc = &ch; printf("%d\n", sizeof(pc));//4 *pc = 'a'; printf("%c\n", ch);//a return 0; } ``` ![](images\c00021.png) ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 10;//向内存申请了4个字节的空间 4个字节在哪里呢 //&a; &为取地址操作符 printf("%p\n", &a);//00EFF888十六进制 1110 1111 1111 1000 1000 1000二进制 //p = &a;//p是一个变量,用来存放地址,叫做指针变量 //指针变量如何定义的 int* p = &a;//说明p的类型是int* 变量名依旧是p printf("%p\n", p);//希望有朝一日通过地址找回去 //如何通过p找到a呢???? *p = 20;//*叫做解引用操作符(间接访问操作符) printf("a = %d\n", a);//20 return 0; } //因为我们在使用内存的时候,经常会使用不同的地址,每次执行程序的时候,变量都会改变空间位置。 //如果别的程序在使用,就需要变换位置空间。 ``` ![](images\c00022.png) ```c #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { double d = 3.14; double* pd = &d; *pd = 5.4; printf("%lf\n", d);//5.400000 printf("%lf\n", *pd); //5.400000 return 0; } ``` ## 1、指针变量大小字节数 ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { double d = 3.14; double* pd = &d;//32个bit位就可存、64bit位就可以存 printf("%d\n",sizeof(pd));//32bit平台4or64bit平台8 return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { printf("%d\n",sizeof(char*));//指针的变量的大小4or8个字节 printf("%d\n", sizeof(short*));//4or8 printf("%d\n", sizeof(int*));//4or8 printf("%d\n", sizeof(double*));//4or8 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { //8个二进制位-占用一个字节 //11是两个16进制位(一个16进制位是4个二进制位) int a = 0x11223344;//占用4个字节 int* pa = &a; char* pc = &a;//char* 会警告 //四个字节存一个地址刚好够 printf("%p\n",pa);//00DBFAEC printf("%p\n",pc);//00DBFAEC return 0; } ``` **指针的意义一:** ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { //8个二进制位-占用一个字节 //11是两个16进制位(一个16进制位是4个二进制位) int a = 0x11223344;//占用4个字节 int* pa = &a; *pa = 0; return 0; } ``` ![](images\c00059.png) **指针的意义二:** ![](images\c00060.png) **指针的价值:** ![](images\c00061.png) ## 2、野指针 野指针不可以正常使用。 局部指针变量不初始化就是野指针。 ```c 第一种情况:案例一 #define _CRT_SECURE_NO_WARNINGS int main() { int a;//局部变量不初始化,默认是随机值 int* b;//局部的指针变量,就被初始化随机值 *b = 20;//野指针 return 0; } 第一种情况:案例二 #define _CRT_SECURE_NO_WARNINGS int main() { //未初始化的指针变量,p里面放的是随机值,里面放的地址就不是有效的地址 int* p;//局部变量不初始化,里面默认值放的是一个随机值 //该随机值就是一个地址 内存里面随便找了个位置,就把这个值分给p *p = 20;//给那块空间分配东西进去 //报错 直接不让用-因为未给p地址指定内存 //即便是执行了,也是非法访问内存,程序也是会崩溃的 //非法宾馆老板打死你哈哈哈哈 return 0; } 第二种情况:越过数组 #define _CRT_SECURE_NO_WARNINGS int main() { int arr[10] = { 0 }; int* p = arr;//p里面存arr首元素的地址 int i = 0; for (i = 0; i <= 12; i++) {//循环13次 *p = i; p++;//越界后,访问内存之外的空间 } return 0; } ``` **数组越界导致的野指针问题:** 一般越界程序崩溃了。 ![](images\c00063.png) **指针指向的内存空间释放:**(两种) 只要返回这种临时变量的地址,都是存在问题的。除非这个临时变量出了局部变量范围不销毁。 ![](images\c00062.png) ## 3、解决野指针的方法 ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int b = 0;//不晓得给b赋予什么的时候,就先放个0摆着 int a = 10; int* pa = &a;//记得一定要初始化 //#define NULL ((void *)0) 0被强制转换成void* 本质上还是0 //NULL使用来初始化空指针的-指针变量初始化 int* p = NULL;//当不知道怎么给指针赋位置的时候,赋个空指针 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 10; int* pa = &a; *pa = 20; //pa指针以后不打算用了,可以把pa置为空 pa = NULL;//不想要一个指针指向谁的时候,或者把所占用的空间还给操作系统了 //避免未来出现野指针 //总结:不用的时候就是空指针,用的时候就不是空指针 //错误写法 //*pa = 10;//赋值为空指针再访问就会报错,程序执行过程中就会崩溃 //正确写法 if (pa != NULL)//如果指针不为空指针的时候,使用它。为空指针就不执行以下代码 { } return 0; } ``` ## 4、指针运算 指针里面放的地址有高地址,低地址,大地址,小地址。地址就是一个数字。 **指针+-整数** ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); int* p = &arr; /* for (i = 0; i < sz; i++) { printf("%d ", *p);//1 2 3 4 5 6 7 8 9 10 p = p + 1;//指针+1 //也可p++; } */ for (i = 0; i < 5; i++) { printf("%d ", *p); //1 3 5 7 9 p = p + 2; } return 0; } ``` ![](images\c00064.jpg) ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; char ch[5] = {0}; printf("%d\n", &arr[9] - &arr[0]);//9 指针-指针=中间元素个数 printf("%d\n", &arr[0] - &arr[9]);//-9 printf("%d\n", &arr[9] - &ch[0]);//err会引起歧义-不知道按照哪个的来进行相减 //指针-指针 两个指针在同一个空间,才有意义 return 0; } ``` ## 5、三种方法求字符串长度 ```c //求字符串长度-strlen-计算器的方式1 //求字符串长度-递归-模拟实现了strlen-递归的方式2 //求字符串长度-指针来求 #define _CRT_SECURE_NO_WARNINGS #include int my_strlen(char* str) { char* start = str; char* end = str; while (*end != '\0') { end++; } return end - start; } int main() { char arr[] = "bit"; int len = my_strlen(arr); printf("%d\n",len);//3 return 0; } ``` ## 6、指针的关系运算 指针的比较大小 **第一种:** ![](images\c00064.png) ![](images\c00065.png) ## 7、指针和数组 ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = {0}; arr;//多数:数组名-首元素的地址 printf("%p\n",arr);//00BDFC24 printf("%p\n",&arr[0]);//00BDFC24 //例外: //1.&arr-&数组名-数组名不是首元素地址-数组名表示整个数组 -&数组名 取出的是整个数组的地址 //2.sizeof(arr) -sizeof(数组名) -数组名表示的是整个数组 -sizeof(数组名)计算的是整个数组的大小 单位是字节 //除此以外,别的任何地方数组名代表的都是首元素的地址 printf("%p\n",&arr);//00BDFC24 确实代表整个数组的地址,只是用首地址来表示 //如何证明呢? printf("%p\n", arr+1);//00BDFC28 整型地址+1,跳过一个整型 printf("%p\n", &arr[0]+1);//00BDFC28 整型地址+1,跳过一个整型 printf("%p\n", &arr+1);//00BDFC4C 数组地址数组+1,跳过整个数组 printf("%d\n",sizeof(arr));//40 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%p ===== %p\n",p+i,&arr[i]); } return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } //方式一 //for (i = 0; i < 10; i++) //{ // printf("%d\n",arr[i]); //} //方式二:数组通过指针来访问 for (i = 0; i < 10; i++) { printf("%d\n",*(p+i)); } return 0; } ``` ## 8、二级指针 ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int a = 0; int* pa = &a; int** ppa = &pa;//ppa就是二级指针 **ppa = 20; printf("%d\n",**ppa);//20 printf("%d\n",a);//20 return 0; } ``` ![](images\c00066.png) ## 9、指针数组 ```c #define _CRT_SECURE_NO_WARNINGS #include void print(int* arr, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(arr + i));//1 2 3 4 5 6 7 8 9 } } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9 }; int sz = sizeof(arr) / sizeof(arr[0]); print(arr,sz); } ``` ```c //很low的用法-开发中不是这么用的 #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 0 };//整型数组-存放整型 char ch[5] = { 0 };//字符数组-存放字符 //同理,指针数组 -存放指针 int* parr[5];//存放整型指针的数组 -指针数组 char* pch[4];//存放字符指针的数组 -指针数组 int a = 10; int b = 20; int c = 30; int d = 40; int* arr1[4] = { &a,&b,&c,&d };//指针数组 int i = 0; for (i = 0; i < 4; i++) { printf("%d ", *(arr1[i]));//10 20 30 40 } return 0; } ``` ```c //数组指针的正确用法 #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr1[] = {1,2,3,4,5}; int arr2[] = {2,3,4,5,6}; int arr3[] = {3,4,5,6,7}; int* parr[] = {arr1,arr2,arr3}; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *parr[i]+j); } printf("\n"); } return 0; } ``` ![](images\c00084.jpg) ```c //指针数组 -存放指针的数组 #define _CRT_SECURE_NO_WARNINGS int main() { int* arr[10]; //arr先和[]结合 -说明是数组 //[10] -说明数组中有10个元素 //int* -数组的元素类型-每一个元素都是int*-说明是指针 char* ch[5];//每一个元素是字符指针 return 0; } ``` ## 10、数组指针 ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { //不知道赋值什么进去,就赋值一个空指针进去 int* pw = NULL;//p是整型指针 -指向整型的指针(可以存放整型的地址) char* pc = NULL;//pc是字符指针 -指向字符的指针(可以存放字符的地址) //同理,数组指针 -指向数组的指针(可以存放数组的地址) int arr1[10] = { 0 }; //arr -首元素的地址 //&arr[0] -首元素的地址 //&arr -数组的地址 int arr2[10] = {1,2,3,4,5,6,7,8,9,10}; //数组的地址存起来 //就得需要一个指针变量,所以定义p为指针变量 //p = &arr2; //int* p = &arr2;//错误写法,因为p是一个整型指针,是不能存放数组地址的 //int* p[10] = &arr2;//p成了一个数组,指针数组就是这么写的。p是数组名 //因为[]的优先级比* 要高 //上面两种均是错误的 //我们需要的是数组指针 int (*p)[10] = &arr2;//这样的话,说明p是指针 剩下int[10] 是数组类型 //这里p指向数组,数组有10个元素,每个元素是一个整型 //上面定义的p就是数组指针 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int *p1[10];//存放指针的数组,因为先和[],说明是数组,类型int*是存放指针的数组 int (*p2)[10];//存放数组的指针,因为先和*,说明是指针,指针指向的数组有10个元素,每个元素是int类型 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS int main() { char* arr[5];//定义指针数组 //char* pa = &arr;//字符指针 //整型地址存到整型指针里面去 //数组地址存到数组指针里面去 // pa是指针变量的名字 //数组指针 (*pa)说明是指针 (*pa)[5]:pa指向数组是5个元素 //每个元素的类型是char* char* (*pa)[5]= &arr; //对比指针数组 int arr1[10] = { 0 }; int (*pa2)[10] = &arr1;//(*pa2)保证pa2是一个指针,指针指向数组,数组10个元素,元素类型是int return 0; } ``` ![](images\c00083.jpg) **数组指针的用途:** ```c //不推荐这样用,尽量二维数组以上再使用 #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,20 }; int (*pa)[10] = &arr; int* p = arr; int i = 0; //方法一: //for (i = 0; i < 10; i++) //{ // printf("%d ",(*pa)[i]);//1 2 3 4 5 6 7 8 9 20 //} //方法二: //for (i = 0; i < 10; i++) //{ // printf("%d ", *(*pa+i));//1 2 3 4 5 6 7 8 9 20 //} //方法三:最方便 for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//1 2 3 4 5 6 7 8 9 20 } return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include //参数是数组的形式 void print1(int arr[3][5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } //参数是指针的形式 void print2(int (*p)[5],int x,int y)//接收一维数组中的地址,数组地址应该放到数组指针中去 {//int (*p)[5] + 1 直接跳一行 int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { //四选一 //printf("%d ", *(*(p + i) + j));//方式一 //printf("%d ",(*(p + i))[j]);//方式二 //printf("%d ", p[i][j]);//方式三 printf("%d ",*(p[i] + j));//方式四 } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; print1(arr, 3, 5);//arr -数组名 - 数组名就是首元素地址 print2(arr, 3, 5);//传递过去的是第一行地址,当成三个元素中的一个 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; int* p = arr; for (i = 0; i < 10; i++) { //四选一 //printf("%d ",arr[i]); //printf("%d ", p[i]); //printf("%d ",*(arr+i)); printf("%d ", *(p + i)); } return 0; } ``` ![](images\c00085.png) ```c #define _CRT_SECURE_NO_WARNINGS int main() { int* p1;//整型指针 -指向整型的指针 char* p2;//字符指针 -指向字符的指针 //数组指针 -指向数组的指针 int arr[5];//定义一个数组 int (*pa)[5] = &arr;//取出数组的地址 //pa[] 数组 肯定不行,得让他先和*结合才是指针,所以在前面加一个* //*pa 结合后说明是一个指针 //[] 指向一个数组 //[5] 数组5个元素 //int 说明数组中每个元素是int类型 //总结:pa就是一个数组指针 //pa的类型就是去掉名字剩下的 int (*)[5] 指向数组的指针类型 int(* parr3[10])[5]; //优先级[]高于* //parr3先和[]结合,说明是数组,数组有10个元素,每一个元素又是数组指针 //int(* )[5]; 类型:数组指针 return 0; } ``` ## 11、数组参数、指针参数 ```c #define _CRT_SECURE_NO_WARNINGS void test(int arr[])//第一种方式 { } void test(int arr[10])//第二种方式 { } void test(int* arr)//第三种方式 { } int main() { int arr[10] = { 0 }; test(arr); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS void test(int* arr[])//数组大小可以省略 {} void test(int* arr[20]) {} void test(int **arr)//一级指针的地址存来刚好放在二级指针里面去 {} int main() { int* arr[20] = { 0 }; test(arr); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS void test(int arr[3][5]) {} void test1(int arr[][5])//省略行 {} void test2(int arr[3][5])//省略列 -缺少下标 切记不能省略列 {} void test3(int arr[1][1])//行和列都省略 -缺少下标 切记不能省略列 {} int main() { int arr[3][5] = { 0 }; test(arr);//二维数组传参 test1(arr);//二维数组传参 test2(arr);//二维数组传参 test3(arr);//二维数组传参 return 0; } ``` 二维数组-首元素地址就是第一行地址 二级指针是用来存放一级指针变量的地址 ```c #define _CRT_SECURE_NO_WARNINGS //void test(int* arr) //{} //void test1(int** arr) //{} void test2(int (*arr)[5])//数组指针 {} int main() { int arr[3][5] = { 0 };//定义一个二维数组 //test(arr);//传递第一行的地址过去 //test1(arr); test2(arr); return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS void test1() {} void test2() {} int main() { int a = 10; int* p1 = &a; char ch = 'w'; char* pc = &ch; test1(&a); test1(p1); test2(&ch); test2(pc); return 0; } ``` ## 12、函数指针 ```c #define _CRT_SECURE_NO_WARNINGS #include //数组指针 -指向数组的指针 //函数指针 -指向函数的指针 存放函数地址的一个指针 int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int arr[10] = { 0 }; //&arr //arr //int (*p)[10] = &arr; //printf("%d", Add(a, b));//30 printf("%p\n",&Add);//002810B4函数的地址 取地址 printf("%p\n", Add);//002810B4函数的地址 函数名 int (*pa)(int,int)= Add; printf("%d\n",(*pa)(2,3));//5 printf("%d\n", (*pa)(5, 3));//8 return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include void Print(char* str) { printf("%s\n",str); } int main() { //函数的返回类型是void void (*p)(char*) = Print;//函数名就是一个地址 指向函数,函数的参数类型是char* (*p)("hello bit");//调用并传参 return 0; } ``` ![](images\c00086.jpg) ![](images\c00087.jpg) ```c #define _CRT_SECURE_NO_WARNINGS void(* signal(int, void(*)( int)) )(int); //signal是一个函数声明 //signal函数的参数有两个,第一个是int类型 第二个是函数指针类型,该函数指针指向的函数时int,返回类型是void //signal函数的返回类型也是一个函数指针:该函数指针指向的函数时int,返回类型是void //简化 typedef void(*pfun_t)(int); pfun_t signal(int, pfun_t); //简化原理类似但有差别 typedef unsigned int uint; ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int(*pa)(int, int) = Add; printf("%d\n",(pa)(2,3));//5 printf("%d\n",(Add)(2,3));//5 //printf("%d\n", *pa(2, 3)); 非法间接寻址 因为不括起来*的优先级时很低的,先和()结合 printf("%d\n",(*pa)(2,3));//5 找到函数解引用并调用 printf("%d\n",(**pa)(2,3));//5 printf("%d\n",(***pa)(2,3));//5 return 0; } ``` ## 13、函数指针数组 ```c #define _CRT_SECURE_NO_WARNINGS #include int Add(int x, int y) { return x + y; }int Sub(int x, int y) { return x - y; }int Mul(int x, int y) { return x * y; }int Div(int x, int y) { return x / y; } int main() { int* arr[5];//指针数组 int (*pa)(int, int) = Add;//Sub/Mul/Div //需要一个数组,这个数组可以存放4个函数的地址 -函数指针数组 //int (*parr[4])(int, int) = Add;//函数指针数组 int (*parr[4])(int, int) = {Add,Sub,Mul,Div};//函数指针数组并初始化 int i = 0; for (i = 0; i < 4; i++) { printf("%d\n", parr[i](2,3));//5 -1 6 0 } return 0; } ``` ```c char* my_strcpy(char* dest, const char* src); //1.写一个函数指针 pf,能够指向my_strcpy char*(*pf)(char*, const char*); //2.写一个函数指针数组 pfArr,能够存放4个my_strcpy函数的地址 char* (*pfArr[4])()(char*, const char*); ``` ## 14、回调函数 ## 12、题目 ```c 1.输出代码 #define _CRT_SECURE_NO_WARNINGS #include int main() { int arr[] = { 1,2,3,4,5 }; //地址名强制类型转换 //short*解引用一次只能够访问两个字节 short* p = (short*)arr; int i = 0; for (i = 0; i < 4; i++) { *(p + i) = 0;//两次循环结束把12的空间全部改为0 } for (i = 0; i < 5; i++) { printf("%d ",arr[i]);//0 0 3 4 5 } return 0; } 2、输出代码 ``` ![](images\c00068.png) ```c 3、输出代码 #define _CRT_SECURE_NO_WARNINGS #include int i;//全局变量-不初始化默认是0 //int i;如果是局部变量就是随机值 int main() { i--; //原码:10000000 00000000 00000000 00000001(-1) //反码:11111111 11111111 11111111 11111110 //补码:11111111 11111111 11111111 11111111 //放到内存中依旧是补码,但是最高位不是无符号位(是有效位,是一个正数),原码=补码=反码 //sizeof(-1)也是4个字节 //sizeof()-计算变量、类型所占内存的大小(大小不可能是负数),所以这里大小恒>=0 //无符号数一般都是>=0,所以sizeof()计算所返回的值就是无符号数 //整数与无符号数要进行计算(比大小、加减乘除)的时候:①先进行把整数i进行转换为无符号数进行计算 //-1转换为无符号数是相当大的 if (i > sizeof(i)) //超大数>4 { printf(">\n");//> } else { printf("<\n"); } return 0; } 4、阅读代码 优化前: #define _CRT_SECURE_NO_WARNINGS #include #include //system的头文件 //求二进制中1的个数:是求补码 //数字在内存中存储是用补码-一个数的补码的二级制序列有多少个1 int count_bit_one(int n)//返回的个数是一个整数,所以用int来定义数据类型 { int count = 0; while (n) { if (n % 2 == 1) { count++; } n = n / 2; } return count; } int main() { int a = 0; scanf("%d",&a); //写一个函数求a的二进制(补码)表示中有几个1 int count = count_bit_one(a); printf("count = %d\n",count); return 0; } //该代码的弊端:负数无法表示 得到结果为0可以按程序推理出来 //原码:10000000 00000000 00000000 00000001(-1) 符号位不变,其他位按位取反 //反码:11111111 11111111 11111111 11111110 反码+1 //补码:11111111 11111111 11111111 11111111 //& :00000000 00000000 00000000 00000001 按位与 //如果把原来的数据右移到最低位,再进行按位与 结果为1原来的结果为1,结果为0原来的结果为0 //要实现就把补码当成无符号位来处理,超大数字%2/2就可以搞定 优化后: #define _CRT_SECURE_NO_WARNINGS #include //方法一 //int count_bit_one(unsigned int n)//unsigned无符号数的意思 int count_bit_one(int n) { int count = 0; int i = 0; for (i = 0; i < 32; i++) { if (((n >> i) & 1) == 1) { count++; } } return count; } int main() { int a = 0; scanf("%d",&a); int count = count_bit_one(a); printf("count = %d\n",count); return 0; } 再次优化:更简便 有几个1就循环几次(上面的都要循环32次) 不考虑溢出的问题 #define _CRT_SECURE_NO_WARNINGS #include #include //system的头文件 //求二进制中1的个数:是求补码 //数字在内存中存储是用补码-一个数的补码的二级制序列有多少个1 int count_bit_one(int n)//返回的个数是一个整数,所以用int来定义数据类型 { int count = 0; while (n) { n = n & (n - 1); count++; } return count; } //n = n&(n-1) //n //13 //1101 n //1100 n-1 //1100 n //1011 n-1 //1000 n //0111 n-1 //0000 n int main() { int a = 0; scanf("%d",&a); int count = count_bit_one(a); printf("count = %d\n",count); return 0; } ``` ## 12、面试题 ==第一道面试题:== ```c //题源:剑指offer————非常经典 #define _CRT_SECURE_NO_WARNINGS #include int main() { char arr1[] = "abcdef"; char arr2[] = "abcdef"; //"abcdef"常量字符串 //理论上讲:常量字符串是不能被修改的 //反正也不能修改,在内存中存储一份就行了 //不能改就拿去用啊,这样还节省空间 char* p1 = "abcdef";//p1存取字符串起始元素a的地址 char* p2 = "abcdef";//p2存取字符串起始元素a的地址 //建议写法--标准写法 //加上const更加具有保护性,更加健壮一些 if (arr1 == arr2) { printf("hehe\n"); } else { printf("haha\n");//创建的空间不同,打印hehe } if (p1 == p2) { //因为都是"abcdef"一模一样,所以没有必要多创建一个空间,指向过去就行 printf("呵呵\n");//打印呵呵 } else { printf("哈哈\n"); } return 0; } ``` ![](images\c00082.png) # 十、结构体 ``` char、int、double....... 人 = 3.14; 不合适 书 - 复杂对象 名字+身高+年龄+身份证号码+... 书名+作者+出版社+定价+书号+.... 复杂对象 - - 结构体 -- 我们自己创造出来的一种类型 struct结构体关键字,用它来描述 ``` ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include //创建一个结构体(书的)类型 struct Book { char name[20];//C语言程序设计 short price;//55 }; int main() { //利用结构体类型创建一个该类型的结构体变量出来 struct Book b1 = {"C语言程序设计",55};//b1就是一本书 printf("书名:%s\n",b1.name);//书名: C语言程序设计 printf("价格:%d元\n", b1.price);//价格: 55元 b1.price = 15; printf("修改后的价格为:%d元\n", b1.price); return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include struct Book { char name[20]; short price; }; int main() { struct Book b1 = { "C语言程序设计",55 }; struct Book* pb = &b1; //利用pb打印出我的书名和价格 printf("%s\n", (*pb).name);//C语言程序设计 printf("%d\n", (*pb).price);//55 printf("%s\n", pb->name);//C语言程序设计 printf("%d\n",pb->price);//55 return 0; } // . 结构体变量.成员 //-> 结构体指针->成员 代码3: #define _CRT_SECURE_NO_WARNINGS 1 #include #include struct Book { char name[20]; short price; }; int main() { struct Book b1 = { "C语言程序设计",55 }; struct Book* pb = &b1; b1.price = 15;//变量可以直接这样赋值 //b1.name = "C++";//数组名name本质上是一个地址 strcpy(b1.name,"C++");//strcpy -string copy 字符串拷贝 库函数 printf("%d\n",b1.price); printf("%s\n", b1.name); return 0; } ``` ```c 写法一: #define _CRT_SECURE_NO_WARNINGS //struct-结构体关键字 stu-结构体标签 struct stu-结构体类型 struct Stu//定义结构体类型 { //成员变量:描述学生的数据 char name[20]; short age; char tel[12]; char sex[5]; }s1,s2,s3; //分号声明是一条语句 //创建类型后就可以在后面创建了s1,s2,s3三个全局的结构体变量(名)-不推荐 //原则:尽量少的使用全局变量 //总结:这里创建的结构体变量会先说明类型-相当于盖房子的图纸-用来描述对象长什么样 int main() { struct Stu s;//s是创建的局部结构体变量 //注意这里不占用空间,例子如下 //int; 不占用空间 //int a;占用空间 return 0; } 写法二: #define _CRT_SECURE_NO_WARNINGS typedef struct Stu//struct Stu整体 { char name[20]; short age; char tel[12]; char sex[5]; }Stu; //typedef 类型重命名 int main() { Stu s1; Stu s2; struct Stu s3; return 0; } ``` ![](images\c00070.png) ```c ``` ```c #define _CRT_SECURE_NO_WARNINGS typedef struct Stu//描述struct stu类型长怎么样 { //结构体成员变量:描述学生的数据 可以是标量(普通变量) char name[20]; short age; char tele[12]; char sex[5]; }Stu;//类型重新起名叫做Stu int main() { struct Stu s1;//局部变量 struct Stu s2; Stu s3; return 0; } ``` ## 1、结构体初始化 ```c #define _CRT_SECURE_NO_WARNINGS #include //定义结构体变量 typedef struct Stu//struct Stu整体 { //成员变量4个 short age; char tel[12]; double d; }Stu; //typedef 类型重命名 struct T { char name[20]; struct Stu s; char* pc; }; int main() { char arr[] = "hello bit\n"; struct T t = { "旺财",{23,"18214601160",3.14},arr}; Stu s1 = { "李",20,"182" };//初始化3个成员 Stu s2; printf("%s\n",t.s.tel); return 0; } ``` ## 2、打印结构体数据 ```c #include typedef struct Stu { char name[20]; short age; char tele[12]; char sex[5]; }Stu; void Print1(Stu tmp) { printf("name:%s\n",tmp.name);//name:李克伟 printf("age:%d\n", tmp.age);//age:23 printf("tele:%s\n",tmp.tele);//tele:18214601160 printf("sex:%s\n",tmp.sex);//sex:男 } void Print2(Stu* ps)//结构体指针 { printf("name:%s\n",ps->name); printf("age:%d\n", ps->age); printf("tele:%s\n", ps->tele); printf("sex:%s\n", ps->sex); } int main() { Stu s = {"李克伟",23,"18214601160","男"}; //打印结构体数据 Print1(s); Print2(&s);//比较节约空间 return 0; } ``` ![](images\c00071.png) ## 3、压栈 ```c #include int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int ret = 0; ret = Add(a, b); printf("%d\n",ret);//30 return 0; } ``` 传递参数的过程会压栈。 函数递归的时候讲过:任何一次函数调用都会在内存中的栈区上申请一块空间。 ![](images\c00072.png) # 十一、数据的存储 ![](images\c00076.png) ## 1、大端和小端 ![](images\c00077.png) ```c #include int main() { int a = 0; char* p = (char*)&a; if (*p == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; } ``` ```c #define _CRT_SECURE_NO_WARNINGS #include int check_sys() { int a = 1; char* p = (char*)&a; //if (*p == 1) // return 1; //else // return 0; return *p; } int main() { //返回1是小端,返回0是大端 int ret = check_sys(); if (ret == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; } ``` ``` #define _CRT_SECURE_NO_WARNINGS #include int check_sys() { int a = 1; return *(char*)&a; } int main() { //返回1是小端,返回0是大端 int ret = check_sys(); if (ret == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; } ``` ## 2、整型提升1 ```c #define _CRT_SECURE_NO_WARNINGS #include int main() { //char因为是%d,所以需要整型提升 //提升之后的结果:11111111 11111111 11111111 11111111 char a = -1;//放进去11111111 //char只能存8个比特位,从右往左拿8个 //11111111 11111111 11111111 11111111(原码) //10000000 00000000 00000000 00000000(反码) //10000000 00000000 00000000 00000001(补码) signed char b = -1;//放进去11111111 //有符号char unsigned char c = -1;//放进去11111111 //无符号char 无符号数提升时,高位补0 //替升前:11111111 //替升后:00000000 00000000 00000000 11111111(正数) printf("a=%d,b=%d,c=%d", a, b, c);//a=-1,b=-1,c=255 return 0; } //10000000 00000000 00000000 00000001 //11111111 11111111 11111111 11111110 //11111111 11111111 11111111 11111111(-1的补码) ``` ## 3、整型提升2 ```c 第一题: #define _CRT_SECURE_NO_WARNINGS #include int main() { char a = -128; //10000000 00000000 00000000 10000000(原码) //11111111 11111111 11111111 01111111(反码) //11111111 11111111 11111111 10000000(补码) //补码存到a里面只能存10000000 printf("%u\n", a);//4294967168 //需要整型提升 因为最高位是1,char又占8个bit,所以全补1 //11111111 11111111 11111111 10000000(整型提升之后的结果) //正常情况下算出原码打印出来 //unsigned int 无符号整型 所以这里原码、反、补码相同 return 0; } 第二题: #define _CRT_SECURE_NO_WARNINGS #include int main() { int i = -20;//有符号数 //10000000 00000000 00000000 00010100(原码) //11111111 11111111 11111111 11101011(反码) unsigned int j = 10;//无符号数 //11111111 11111111 11111111 11101100(-20的补码) //00000000 00000000 00000000 00001010(10的补码) //11111111 11111111 11111111 11110110(结果) printf("%d\n", i + j);//-10 //%d进行打印,需要拿出原码进行打印才行 //11111111 11111111 11111111 11110101-反码 //10000000 00000000 00000000 00001010-原码(-10) return 0; } 第三题:代码 #define _CRT_SECURE_NO_WARNINGS #include #include int main() { unsigned int i; for (i = 9; i >= 0; i--) { printf("%u\n", i); Sleep(100);//打印完睡眠100ms就是0.1s } return 0; } ``` ------ # 终结篇:面试题 ## **1、C++面试题:写代码交换两个整数的值** 题目:交换两个int整型变量的值,不能使用第三个变量。即a = 3,b =5,交换之后,a = 5,b = 3; ```c (1)引入第三个变量:进入企业会启用临时变量,第三个变量的方法,因为这种方法可读性较高,执行效率比较高。 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 3; int b = 5; int c = 0;//引入一个空瓶,也就睡临时变量 printf("交换前:a = %d b = %d\n", a, b); c = a;//c=3 a = b;//a=5 b = c;//b=3 printf("交换后:a = %d b = %d\n", a, b); return 0; } ``` ```c (2)加减的方法:(缺点:整型溢出问题) #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int a = 3; int b = 5; b = a + b;//b=8,a=3 a = b - a;//b=8,a=5 b = b - a;//b=3,a=5 printf("a = %d b = %d\n",a,b); return 0; } 因为int类型的变量占用4个字节,占用32个bit位的空间 32个bit位空间一定会有其最大值: #define _CRT_SECURE_NO_WARNINGS 1 #include #include //引入该头文件,就可以查看INT_MAX,选中右键选中“转到定义” int main() { INT_MAX;//整型的最大值,这个值实际上是在某个头文件里面放着 2147483647 return 0; } ``` ```c (3)按位异或:可读性差,执行效率低于其他方法 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() {//不会溢出,没有使用第三个变量 int a = 3;//二进制序列011 int b = 5;//二进制序列101 printf("交换前:a = %d b= %d\n",a,b); b = a ^ b;//a=110 a = a ^ b;//b=011 b = a ^ b;//a=101 printf("交换后:a = %d b= %d\n",a,b); return 0; } ``` ## 2、LeetCode136题目找出单身狗 题面:给定一个非空整型数组除了某个元素只出现一次外,其余每个元素均出现两次,找出那个只出现了一次的元素。 样例:int[a] = {1,2,3,4,5,1,2,3,4},该数组中只有5出现一次,其他数字都是成对出现,要找出5. ```c 暴力求解:循环次数过多,两个循环 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[9] = { 1,2,3,4,5,1,2,3,4 };//找出单身狗 //统计每个元素在数组中出现的次数 int i = 0; int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的元素个数 for (i = 0; i < sz; i++) { //printf("数组元素列表:%d\n",arr[i]); //统计arr[i]在数组中出现了几次; int count = 0;//计数器 int j = 0; for (j = 0; j < sz; j++) { if (arr[i] == arr[j]) { count++; } } if (count == 1) { printf("单身狗是:%d\n", arr[i]); break; } } return 0; } ``` ```c //3^3=0,5^5=0,a^a=0 // 0^a=a a^b^a=b // 异或满足交换律 //3的二级制是011,5的二进制是101 //同理:这组数全部异或可以找出单身狗 1^1^2^2^3^3^4^4^5=5 #define _CRT_SECURE_NO_WARNINGS 1 #include int main() { int arr[] = { 1,2,3,4,5,1,2,3,4,7,5 }; int i = 0; int ret = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { ret = ret ^ arr[i]; } printf("单身狗: %d\n", ret); return 0; } ``` ## **3、关机程序** 程序运行,你的电脑在1分钟后关机,如果输入,我是李克伟,就取消关机。 电脑上有一个命令提示符,就是cmd(command命令行) ```c shutdown -s -t 60 //60后关机 -s关机 -t时间 关机 shutdown -a 取消关机 ``` ```c 代码1: #define _CRT_SECURE_NO_WARNINGS 1 #include #include //ststem #include //strcmp int main() { char input[20] = { 0 };//存储数据 字符串最好放到字符数组里面去 //system() 专门用来执行系统命令的 system("shutdown -s -t 60"); again: printf("请注意,你的电脑在1分钟内将会关机,如果输入:'我是李克伟',就取消关机\请输入>:"); scanf("%s",input);//%s对应字符串 if (strcmp(input, "我是李克伟") == 0) //判断input中放的是不是“我是李克伟” strcmp库函数 -string compare 字符串比较函数 { system("shutdown -a");//库函数 } else { goto again;//调到again那里 } return 0; } 代码2: #define _CRT_SECURE_NO_WARNINGS 1 #include #include #include int main() { char input[20] = { 0 }; system("shutdown -s -t 60"); while (1) { printf("请注意,你的电脑在1分钟内将会关机,如果输入:我是李克伟,就取消关机\n请输入>:"); scanf("%s", input); if (strcmp(input, "我是李克伟") == 0) { system("shutdown -a"); break; } } return 0; } ``` 搜索服务,右击“服务”属性,可以查看更多。 ## 4、猜数字游戏 ```c #define _CRT_SECURE_NO_WARNINGS 1 #include #include #include void menu()//函数被调用,立马打印该菜单 { printf("*************************************************\n"); printf("*** 1.play(玩游戏) 0.exit(退出游戏) *****\n"); printf("*************************************************\n"); } void game()//该函数的返回类型是void { int ret = 0; int guess = 0; printf("猜数字\n"); //1、生成随机数 //srand(1);//防止每次使用的随机数起点都是一样的 //给随机数里面添加随机数-不方便-电脑上的时间随时在发生变化-时间戳 //当前计算机的时间-计算机的起始时间(1970.1.1.0:0:0)=(xxxx)秒 //拿时间戳来设置随机数的生成起始点 //time函数返回值是time_t类型 //time_t time(time_t* timer);//int*整型指针 time_t*型指针 //time_t//右击鼠标点击转到定义 typedef __time64_t time_t; 对__time64_t进行重命名 发现是长整型 //放到主函数里面去srand((unsigned int)time(NULL));//(unsigned int)强制转化 (NULL)空指针 ret = rand()%100+1;//生成 随机数函数 int rand( void表示函数无参 ); 函数返回一个整型 //RAND_MAX 在0- 32767之间 //建议生成1-100之间的随机数 printf("%d\n",ret); //2、猜数字 while (1) { printf("请猜数字:>"); scanf("%d", &guess); if (guess > ret) { printf("猜大了\n"); } else if(guess < ret) { printf("猜小了\n"); } else { printf("恭喜你,猜对了\n"); break; } } } int main() { //do...while循环游戏至少执行一次。因为我们至少需要登录游戏,进入游戏一次。所以使用do...while //但是我们又要反复玩,就是需要该游戏不断的重复循环 int input = 0; srand((unsigned int)time(NULL));//整个函数用都行 do { menu();//菜单函数 printf("请选择>: "); scanf("%d",&input);//变量的这个地方得取地址& switch (input) { case 1: game();//玩游戏-猜数字游戏 break;//再来一次 case 0: printf("退出游戏\n"); break; default: printf("您输入的数字有误\n"); break; } } while (input); return 0; } ```