ACM金牌带你零基础直达C语言精通-课程资料
本笔记属于船说系列课程之一,课程链接:ACM金牌带你零基础直达C语言精通https://www.bilibili.com/cheese/play/ep159068?csource=private_space_class_null&spm_id_from=333.999.0.0
你也可以选择购买『船说系列课程-年度会员』产品『船票』,畅享一年内无限制学习已上线的所有船说系列课程:船票购买入口https://www.bilibili.com/cheese/pages/packageCourseDetail?productId=598
做题网站OJ:HZOJ - Online Judge
Leetcode :力扣 (LeetCode) 全球极客挚爱的技术成长平台
一.三个标准流:stdin、stdout、stderr
标准输出流(stdout)
对应的文件表示符为:1
对于为什么打印一行字符串为什么会默认打印倒终端中,因为操作系统把标准输出流定向到了终端环境中。
程序可以使用标准输出函数(如
printf
、puts
等)将数据输出到stdout
,从而在终端上显示结果。标准输入流(stdin)
对应的文件表示符为:0
默认在终端环境中进行读取数据。
用户可以通过键盘向程序提供输入,该输入会被发送到
stdin
中。程序可以使用标准输入函数(如scanf
、fgets
等)从stdin
中读取数据。标准错误输出流(stderr)
对应的文件表示符为:2
默认输出定向是终端环境中。
程序可以使用标准错误输出函数(如
fprintf(stderr, ...)
)将错误信息输出到stderr
,从而在终端上显示错误。三个流的缓冲区
代码演示:
1.对于3种流的最基本的使用:
#include<stdio.h> int main() { //利用标准输出流输出 printf("hello world\n");// stdout int n; //利用标准输入流进行读取数据 scanf("%d", &n);// stdin printf("n = %d\n", n); //利用了标准错误输出流进行输出数据 perror("out of range\n");//stderr return 0; }
代码执行结果:
2.对于缓冲区的认识:
这段代码是可以执行的:
#include<stdio.h> int main() { printf("alskaskldjaslkdjlaskld123"); *((int *)(0)) = 5; return 0; }
执行结果:
可以看到终端上没有打印printf函数内的内容,但不能说明printf函数那句代码是有错误的,只是内容还在输出缓冲区里面,还没有进行输出,程序就执行崩溃了。
继续往下看:
在printf函数内容种最后加上一个换行符(\n),看输出结果:
因为遇到换行符
\n
时会触发缓冲区的刷新,即将缓冲区中的数据输出到终端中。这就体现了缓存区的作用。
另一种方法就是使用fflush进行对内容输出:
#include<stdio.h> int main() { printf("alskaskldjaslkdjlaskld123\n"); //stdout在操作系统种也是文件 //利用fflush函数进行对stdout文件进行冲刷,也就是刷新他的缓冲区 //然后内容就会打印到终端上 fflush(stdout); *((int *)(0)) = 5; return 0; }
执行结果:
窥探标准流fscanf和fprintf
程序中如何使用:
#include<stdio.h> int main() { //向标准输出流种输出hello world fprintf(stdout, "hello world\n"); //向标准输入流种读入数据 int n; fscanf(stdin, "%d", &n); //向标准错误输出流种打印n的值 fprintf(stderr, "n = %d\n", n); return 0; }
执行结果:
标准流的重定向:
如何讲输出内容重定向输出到一个文件中?
两种方式
第一种方法:
是用freopen函数进行重定向:
如何使用:
#include<stdio.h> int main() { //参数一需要输出的新文件名 //参数二以打开文件的什么模式 //参数三需要重新定向的文件指针 freopen("output.txt", "w", stdout); //r代表读文件,w代表写文件 freopen("input.txt", "r", stdin); //r代表读文件,w代表写文件 printf("hello freopen yes\n"); char s[1000] = {0}; //当读入到文件结尾时,循环结束 while (scanf("%[^\n]", s) != EOF) { //getchar()从标准输入流中读取一个字符 //这里为什么要使用到getchar() //因为在使用scanf时 //里面的字符匹配集是非\n就读取 //那么在最后遇到\n的时候结束读取 //最后输入流中会存留一个\n //然后scanf一直不读取 //最后造成了while死循环 //所以在这需要使用一个getchar()来对\n进行处理掉 getchar(); printf("%s | hello world\n", s); } return 0; }
input.txt(这个文件是需要自己创建,并在里面写东西的):
ni hao CSDN GCC Good
执行前,只有input.txt和源代码文件:
执行后(a.out是源代码编译后的可执行程序):
发现output.txt在执行程序后被创建出来,再看看output.txt中的内容:
和程序设计还有想法是一样的结果。
第二种方法:
在命令中执行重定向(只能在linux环境下可以使用)
需要用到的源代码:
#include<stdio.h> int main() { char s[1000] = {0}; while (scanf("%[^\n]", s) != EOF) { getchar(); printf("%s | hello world\n", s); } }
然后进行对源文件的编译:
在进行对执行文件的标准输出流重定向到output.txt文件中, 因为是输出所以需要指向目标文件
在输入的最后,需要手动打入一个文件结束,ctrl + D, 表示输入结束。
然后打开output.txt文件查看:
在试一下对执行文件的标准输入流重定向, 因为是需要读入所以需要目标文件是可执行程序,需要指向自己
用命令行重定向标准错误输出流:
#include<stdio.h> int main() { char s[1000] = {0}; int i = 1; while (scanf("%[^\n]", s) != EOF) { getchar(); fprintf(stderr, "%d test\n", i++); printf("%s | hello world\n", s); } }
执行命令:
2是表示标准错误流的文件表示符,然后讲标准错误输出流重定向到output_err.txt文件中.
执行效果:
重学scanf
scanf函数读入缓存区
先用scanf读入两个整形:
现在在缓冲区中有一段内容,然后进行读取,知道读到第一个非法字符.
当读入到空格时,空格就是这个非法字符,此时停止,然后把123转为整形赋值给变量a
然后会跳过空白符,也就是空格,然后再进行读取,在次读到非数字字符时会进行停止,然后给变量b进行赋值.
最后结果,然后缓存区中只有换行符.
scanf读入%c
在当前缓存区中,%c会读取456前面的空格字符,而不是读入'4'这个字符.
下面用程序进行演示:
#include<stdio.h> int main() { char c1, c2; int a, b; scanf("%d", &a); scanf("%c%c", &c1, &c2); scanf("%d", &b); printf("a = %d, b = %d\n", a, b); printf("c1 = %d, c2 = %d\n", c1, c2); return 0; }
执行结果:
处理scanf读入残值的技巧
利用getchar()吞掉一个字符
下面的代码演示:
getchar()的用法
#include<stdio.h> int main() { int a, b; scanf("%d%d", &a, &b); //利用getchar吞掉换行符 getchar(); char c = 'x'; scanf("%c", &c); printf("a = %d, b = %d, c = %c\n", a, b, c); return 0; }
执行结果:
利用getchar() 吞掉\n符,准确的给字符c进行赋值.
二.实现一个printf函数
第一步:实现printf函数声明:
#include<stdio.h> int my_printf(const char *format, ...) { } int main() { return 0; }
第二步:实现输出"hello world"功能
还处理了一个问题%特殊情况处理.
#include<stdio.h> //这个宏定义是测试printf和my_printf区别 #define TEST(format, args...) {\ int n1, n2;\ n1 = printf(format, ##args);\ n2 = my_printf(format, ##args);\ printf("n1 = %d, n2 = %d\n", n1, n2);\ } int my_printf(const char *format, ...) { #define PUTC(c) putchar(c), cnt += 1 //cnt代表输出了多少个字符 int cnt = 0; for (int i = 0; format[i]; i++) { switch (format[i]) { //如果当前字符是%,那么就需要对下个字符进行特殊处理 case '%': { switch (format[i + 1]) { //当有两个%时,那就需要打印一个% case '%': { PUTC(format[i + 1]); //对于下个位置已经处理所以需要i+1 i++; } break; } } break; default : PUTC(format[i]); break; } //putchar() 是向屏幕上打印一个字符 } return cnt; } int main() { //测试printf和my_printf输出hello world TEST("hello world\n"); //对于printf%%输出就是一个% //由于printf对%是特殊处理,那么对于my_printf也就需要特殊处理 TEST("100%%\n"); return 0; }
执行结果:
第三步实现%s输出功能
#include <stdio.h> #include <stdarg.h> //这个宏定义是测试printf和my_printf区别 #define TEST(format, args...) {\ int n1, n2;\ n1 = printf(format, ##args);\ n2 = my_printf(format, ##args);\ printf("n1 = %d, n2 = %d\n", n1, n2);\ } int my_printf(const char *format, ...) { #define PUTC(c) putchar(c), cnt += 1 va_list args;//args表示变参列表 va_start(args, format);//变参列表从format参数后面开始 //cnt代表输出了多少个字符 int cnt = 0; for (int i = 0; format[i]; i++) {//这里中间条件用到了format[i],当format[i] = '\0'时表示循环结束 switch (format[i]) { //如果当前字符是%,那么就需要对下个字符进行特殊处理 case '%': { switch (format[i + 1]) { //当有两个%时,那就需要打印一个% case '%': { PUTC(format[i + 1]); //对于下个位置已经处理所以需要i+1 i++; } break; //%后接s那么就是字符串的格式占位符 //然后这里就需要用到变参列表的解析 case 's': { //获取变参列表的参数,并且该参数的类型为char *类型 const char *s = va_arg(args, const char *); //获取到字符串后,进行打印字符串 for (int j = 0; s[j]; j++) PUTC(s[j]); //s这个字符被处理掉了,不需要打印了,位置需要往后移 i++; } break; } } break; default : PUTC(format[i]); break; } //putchar() 是向屏幕上打印一个字符 } return cnt; } int main() { //测试printf和my_printf输出hello world TEST("hello world\n"); //对于printf%%输出就是一个% //由于printf对%是特殊处理,那么对于my_printf也就需要特殊处理 TEST("100%%\n"); TEST("%s\n", "hello world 100 %%"); return 0; }
执行结果:
第四步实现%d输出功能
#include <stdio.h> #include <inttypes.h> #include <stdarg.h> #include <math.h> #include <limits.h> //这个宏定义是测试printf和my_printf区别 #define TEST(format, args...) {\ int n1, n2;\ n1 = printf(format, ##args);\ n2 = my_printf(format, ##args);\ printf("n1 = %d, n2 = %d\n", n1, n2);\ } int my_printf(const char *format, ...) { #define PUTC(c) putchar(c), cnt += 1 va_list args;//args表示变参列表 va_start(args, format);//变参列表从format参数后面开始 //cnt代表输出了多少个字符 int cnt = 0; for (int i = 0; format[i]; i++) {//这里中间条件用到了format[i],当format[i] = '\0'时表示循环结束 switch (format[i]) { //如果当前字符是%,那么就需要对下个字符进行特殊处理 case '%': { switch (format[i + 1]) { //当有两个%时,那就需要打印一个% case '%': { PUTC(format[i + 1]); //对于下个位置已经处理所以需要i+1 i++; } break; //%后接s那么就是字符串的格式占位符 //然后这里就需要用到变参列表的解析 case 's': { //获取变参列表的参数,并且该参数的类型为char *类型 const char *s = va_arg(args, const char *); //获取到字符串后,进行打印字符串 for (int j = 0; s[j]; j++) PUTC(s[j]); //s这个字符被处理掉了,不需要打印了,位置需要往后移 i++; } break; //%后接d那么就是整形的格式占位符 case 'd': { //获取变参列表的参数,并且该参数的类型为char *类型 const int num = va_arg(args, const int); //这里就需要率到如何进行把每一位数字进行输出 //因为putchar只能打印单个字符 //那么就用数组将数字进行存储起来 int8_t arr[20];//int8_t表示只有8位带符号的整形数字,只占8bit位,头文件<inttype.h> //现在进行对num数字每位进行存储在数组中 int n = num, ind = 0; if (n < 0) PUTC('-');//n为负数时需要输出负号 do { //从个位进行每位存储在数组中 arr[ind++] = n % 10; n /= 10; } while(n);//因为n = 0不会进入循环,那么就无法进行输出,所以使用do while循环先执行一次 //因为个位在第一位,所以需要倒过来输出 for (; ind > 0; ind--) { //ind - 1是因为,当前ind位置是空的,最高位在ind - 1上 //当num为负数时需要,将他变为正数进行输出 if (num < 0) PUTC(-arr[ind - 1] + '0'); else PUTC(arr[ind - 1] + '0'); } i++;//与之前的操作同理 } } } break; default : PUTC(format[i]); break; } //putchar() 是向屏幕上打印一个字符 } return cnt; } int main() { //测试printf和my_printf输出hello world TEST("hello world\n"); //对于printf%%输出就是一个% //由于printf对%是特殊处理,那么对于my_printf也就需要特殊处理 TEST("100%%\n"); TEST("%s\n", "hello world 100 %%"); int a = 123; TEST("a = %d\n", a); a = 0; TEST("a = %d\n", a); a = INT_MAX; TEST("a = %d\n", a); a = INT_MIN; TEST("a = %d\n", a); return 0; }
执行结果:
这里我只截取了关于实现%d输出功能的结果
可以发现我们实现的my_printf对于正数,负数,0都可以输出
第五步实现%x输出功能:
#include <stdio.h> #include <inttypes.h> #include <stdarg.h> #include <math.h> #include <limits.h> //这个宏定义是测试printf和my_printf区别 #define TEST(format, args...) {\ int n1, n2;\ n1 = printf(format, ##args);\ n2 = my_printf(format, ##args);\ printf("n1 = %d, n2 = %d\n", n1, n2);\ } char get_16_code(int n) { //当这一位小于10,在16进制中还是用数字表示 if (n < 10) return n + '0'; //当这一位大于10,在16进制中需要用字母表示 //a 10, b 11, c 12, d 13, e 14, f 15 return n - 10 + 'a'; } int my_printf(const char *format, ...) { #define PUTC(c) putchar(c), cnt += 1 va_list args;//args表示变参列表 va_start(args, format);//变参列表从format参数后面开始 //cnt代表输出了多少个字符 int cnt = 0; for (int i = 0; format[i]; i++) {//这里中间条件用到了format[i],当format[i] = '\0'时表示循环结束 //putchar() 是向屏幕上打印一个字符 switch (format[i]) { //如果当前字符是%,那么就需要对下个字符进行特殊处理 case '%': { switch (format[i + 1]) { //当有两个%时,那就需要打印一个% case '%': { PUTC(format[i + 1]); //对于下个位置已经处理所以需要i+1 i++; } break; //%后接s那么就是字符串的格式占位符 //然后这里就需要用到变参列表的解析 case 's': { //获取变参列表的参数,并且该参数的类型为char *类型 const char *s = va_arg(args, const char *); //获取到字符串后,进行打印字符串 for (int j = 0; s[j]; j++) PUTC(s[j]); //s这个字符被处理掉了,不需要打印了,位置需要往后移 i++; } break; //%后接d那么就是整形的格式占位符 case 'd': { //获取变参列表的参数,并且该参数的类型为char *类型 const int num = va_arg(args, const int); //这里就需要率到如何进行把每一位数字进行输出 //因为putchar只能打印单个字符 //那么就用数组将数字进行存储起来 int8_t arr[20];//int8_t表示只有8位带符号的整形数字,只占8bit位,头文件<inttype.h> //现在进行对num数字每位进行存储在数组中 int n = num, ind = 0; if (n < 0) PUTC('-');//n为负数时需要输出负号 do { //从个位进行每位存储在数组中 arr[ind++] = n % 10; n /= 10; } while(n);//因为n = 0不会进入循环,那么就无法进行输出,所以使用do while循环先执行一次 //因为个位在第一位,所以需要倒过来输出 for (; ind > 0; ind--) { //ind - 1是因为,当前ind位置是空的,最高位在ind - 1上 //当num为负数时需要,将他变为正数进行输出 if (num < 0) PUTC(-arr[ind - 1] + '0'); else PUTC(arr[ind - 1] + '0'); } i++;//与之前的操作同理 } break; case 'x': { //因为十六进制输出是不带符号的,所以需要用到无符号整形 unsigned int num = va_arg(args, unsigned int); int8_t arr[20], ind = 0; do { //因为需要转换为16进制,所以需要对16进行取余 arr[ind++] = num % 16; num /= 16; } while(num); for (; ind > 0; ind--) { //实现一个方法将10进制转为16进制 PUTC(get_16_code(arr[ind - 1])); } i++;//同理 } break; } } break; default : PUTC(format[i]); break; } } return cnt; } int main() { //测试printf和my_printf输出hello world TEST("hello world\n"); //对于printf%%输出就是一个% //由于printf对%是特殊处理,那么对于my_printf也就需要特殊处理 TEST("100%%\n"); TEST("%s\n", "hello world 100 %%"); int a = 123; TEST("a = %d\n", a); a = 0; TEST("a = %d\n", a); a = INT_MAX; TEST("a = %d\n", a); a = INT_MIN; TEST("a = %d\n", a); TEST("123 = %x\n", 123); TEST("-1 = %x\n", -1); TEST("INT_MAX = %x\n", INT_MAX); TEST("INT_MIN = %x\n", INT_MIN); return 0; }
执行结果:
总结:
对于printf的功能,肯定没有实现完整,但是对于常用的用法都是实现了,在没有实现的用法,你可以下去自己尝试去实现那些功能.
本章小结:
对于标准输出和输入在本章有了新的认识和理解,对于scanf和printf函数也有了新的认识.
在最后的实现printf函数,看完课程后跟着船长实现了一遍,然后在自己通过自己的想法再来实现一遍,这样更加巩固了你所学的知识.
最后加油,看最后加油,看到这里你已经超过百分之95的人了。