前言
我的第一门语言就是C,但是学艺不精,中途跑去学了C#和Java后,感觉到了C的重要性,毕竟是最接近底层的语言,又跑回来学C。
毕竟前两门的控制语句,变量什么的都是类似的,回到C后只需要学习一些特定C的语法,比如宏,预编译指令等等,这些对我来说都是陌生的词汇。
所以边学边记录一下以前的知识。
一、typedef
typedef是数据类型定义的关键字,它用于给已有的数据类型取一个新的名称。typedef通常用于简化复杂的数据类型定义、增加程序的可读性和可维护性。
具体地说,使用typedef可以实现以下几个方面的功能:
1.简化复杂数据类型:对于一些比较复杂、难以阅读和理解的类型定义,我们可以利用typedef为其起一个更加清晰明了、易于理解的名称,提高代码的可读性。
例如:
// 使用typedef之前 struct Books { char title[50]; char author[50]; char subject[100]; int book_id; } Book;//在java里,这个Book就相当于一个类的对象名 int main( ) { strcpy( Book.title, "C 教程"); strcpy( Book.author, "Runoob"); strcpy( Book.subject, "编程语言"); Book.book_id = 12345; printf( "书标题 : %s\n", Book.title); printf( "书作者 : %s\n", Book.author); printf( "书类目 : %s\n", Book.subject); printf( "书 ID : %d\n", Book.book_id); return 0; } // 使用typedef之后------------------------------- typedef struct Books { char title[50]; char author[50]; char subject[100]; int book_id; } Book;//在java里,这个Book就相当于一个类 int main( ) { Book book; strcpy( book.title, "C 教程"); strcpy( book.author, "Runoob"); strcpy( book.subject, "编程语言"); book.book_id = 12345; printf( "书标题 : %s\n", book.title); printf( "书作者 : %s\n", book.author); printf( "书类目 : %s\n", book.subject); printf( "书 ID : %d\n", book.book_id); return 0; }
在这个例子中,我们用typedef将原本比较冗长的结构体定义简化为了一个更加容易理解的Book
类型。
2.声明复合数据类型:当我们需要把多个基本数据类型组合起来形成一个新的、自定义的数据类型时,可以使用typedef来声明这种新型数据类型。
例如:
// 定义一个包含姓名和成绩两个字段的学生信息 struct student { char* name; float score; }; // 利用 typedef 声明一个新类型 StudentInfo 来表示学生信息 typdef struct student StudentInfo;
在这个例子中,我们将原始结构体student封装成了一个新型StudentInfo数据类型,并可以按照此种类型声明变量。
3.消除代码重复:有些场景下,同一个程序中需要多次使用相同的数据类型定义,这时可以使用typedef来避免代码的重复。
例如:
// 定义一个返回两个整数之和的函数指针类型 int (*SumFuncPtr)(int a, int b); // 此处再次使用 SumFuncPtr 类型时,就可以直接用 typedef 定义 typdef int (*SumFuncPtr)(int a, int b);
在这个例子中,我们把两次相同的函数指针类型定义合并为了一个typedef语句。
总之,在实际开发过程中,typedef能帮助我们更形象地表示某一类数据,提高可读性、可维护性和代码复用性。
二、#define
这里简要的概述一下#define,到后面预编译的时候会详解。
#define是C系语言中的一个预处理指令,主要用于给常量、表达式和函数等定义一个名字,这样可以提高程序的可读性和维护性。
使用#define定义的常量也被称为宏常量,使用方法类似于普通常量。
主要作用如下:
1.定义常量:可以使用#define来定义不变的值或符号,方便代码中多次使用。
例子:
#define MAX_NUM 100 int array[MAX_NUM];
2.定义带参数的宏:可以使用#define来定义带参数的宏,方便在代码中进行简单替换。
例子:
#define SQUARE(x) ((x) * (x)) int main() { int a = 5; int b = SQUARE(a); // b = ((a) * (a)) = 25 return 0; }
3.定义条件编译:可以使用#define来定义条件编译指令,根据条件来选择编译哪些代码。这样可以写出能够适应不同操作系统或硬件平台的程序。
例子:
#define WINDOWS #ifdef WINDOWS #include <windows.h> #else #include <unistd.h> #endif
4.定义函数或类型名称:可以使用#define
来为复杂表达式或类型声明定义一个简短的别名。这样可以提高代码可读性和易维护性。
例子:
typedef struct Student { int id; char name[20]; } Stu; #define MAX_NUM 100 Stu student_array[MAX_NUM];
三、输入&输出
大家肯定有过printf() 用于格式化输出到屏幕的经历,这个就是输出。
C 语言中的 I/O (输入/输出) 通常使用 printf() 和 scanf() 两个函数。
scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕)。
%f 格式化输入并输出浮点型数据
#include <stdio.h> int main() { float f; printf("Enter a number: "); // %f 匹配浮点型数据 scanf("%f",&f); printf("Value = %f", f); return 0; }
1)格式控制符
在 C 语言中,格式控制的功能由标准库函数 printf 和 scanf 提供。printf 可以输出指定格式的文本内容,而 scanf 则可以从输入流(通常是键盘)读取指定格式的数据。
以下是一些常用的格式控制符号:
%d 或 %i: 把整数类型的值插入到输出中;
%f 或 %F: 把浮点数类型的值插入到输出中;
%c: 把字符类型的值插入到输出中;
%s: 把字符串类型的值插入到输出中;
%o: 以八进制形式输出整数类型的值;
%x、%X:以十六进制形式输出整数类型的值;
%u: 输出无符号类型的十进制整数。
特殊的格式控制符
%8s是一个 printf 函数的格式控制符,在字符串类型的输出中非常有用。这个格式控制符告诉 printf 函数把字符串占据 8 个字符的位置,并在左侧填充空格,以便更好地对齐输出。
具体来说,%8s的意思是输出一个长度为 8 个字符的字符串,如果字符串的实际长度小于 8,则在输出时在左侧填充空格,保证总共占据 8 个字符的位置(向右对齐)。
那%8s呢?
母庸质疑,肯定是向左对齐啦!
例如,下面的代码使用了%8s格式控制符来输出一列水果名称:
#include <stdio.h> int main() { printf("%-8s|%-8s|%-8s\n", "Apple", "Banana", "Peach"); printf("%-8s|%-8s|%-8s\n", "Orange", "Watermelon", "Mango"); return 0; }
在这段代码中,“%-8s”告诉 printf 将每个字符串都输出为长度为 8 的字符串, - 符号表示左对齐。如果字符串的长度少于 8,则在其右侧填充空格,而如果字符串长度大于 8,则超出部分直接被截断。
Apple |Banana |Peach Orange |Watermelon|Mango
除开格式控制符,对于其他的输入输出函数也是有的,简要介绍两个:
2)getchar() & putchar() 函数
int getchar(void)函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c)函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
#include <stdio.h> int main( ) { int c; printf( "Enter a value :"); c = getchar( ); printf( "\nYou entered: "); putchar( c ); printf( "\n"); return 0; }
输入runoob,结果如下:
$./a.out
Enter a value :runoob
You entered: r
四、文件读写
一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。
C 语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。
1)打开文件
fopen()函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象, FILE 包含了所有用来控制流的必要的信息。
下面是这个函数调用的原型:
FILE *fopen( const char *filename, const char *mode );
在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
“rb”, “wb”, “ab”, “rb+”, “r+b”, “wb+”, “w+b”, “ab+”, “a+b”
2)关闭文件
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。EOF 是一个定义在头文件 stdio.h 中的常量。
这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。
3)写入文件
1.fputc
int fputc( int c, FILE *fp );
函数fputc()把整数 c 的值写到 fp 所指向的**输出流(文件)**中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。
2.fprintf
int fprintf(FILE *fp,const char *format, …)
函数把一个字符串写入到文件中。
3.综合案例
#include <stdio.h> int main() { FILE *fp = NULL; fp = fopen("/tmp/test.txt", "w+"); fprintf(fp, "This is testing for fprintf...\n"); fputs("This is testing for fputs...\n", fp); fclose(fp); }
当上面的代码被编译和执行时,它会在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函数写入两行。接下来让我们来读取这个文件。
注意:
● 请确保您有可用的 tmp 目录,如果不存在该目录,则需要在您的计算机上先创建该目录。
● /tmp 一般是 Linux 系统上的临时目录,如果你在 Windows 系统上运行,则需要修改为本地环境中已存在的目录,例如: C:\tmp、D:\tmp等。
4)读取文件
1.fgetc
int fgetc( FILE * fp );
fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF。
2.fgets
char *fgets( char *buf, int n, FILE *fp );
函数fgets()从fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在最后追加一个null字符来终止字符串。
如果这个函数在读取最后一个字符之前,就遇到换行'\n'或文件末尾,则返回读取到的字符,包括换行符。
3.fscanf
int fscanf(FILE *fp, const char *format, …)
可以指定读取多少字符。
遇到空格和换行时,停止读取。
```c #include <stdio.h> int main() { FILE *fp = NULL; fp = fopen("/tmp/test.txt", "w+"); fprintf(fp, "This is testing for fprintf...\n"); fputs("This is testing for fputs...\n", fp); fclose(fp); }
```c #include <stdio.h> int main() { FILE *fp = NULL; char buff[255]; fp = fopen("/tmp/test.txt", "r"); fscanf(fp, "%s", buff); printf("1: %s\n", buff ); fgets(buff, 255, (FILE*)fp); printf("2: %s\n", buff ); fgets(buff, 255, (FILE*)fp); printf("3: %s\n", buff ); fclose(fp); }
1: This
2: is testing for fprintf…
3: This is testing for fputs…
首先,fscanf() 方法只读取了 This,因为它在后边遇到了一个空格。其次,调用 fgets() 读取剩余的部分,直到行尾。最后,调用 fgets() 完整地读取第二行。
5)二进制 I/O 函数
这两个函数都是用于存储块的读写 - 通常是数组或结构体。
size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file); size_t fwrite(const void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);