C programming

Introduction to C programming

Posted by Yingshan Li on November 18, 2019

前言

C语言是由Dennis Ritchie与1972年在贝尔实验室开发出来的。Unix操作系统、C编译器和绝大部分的unix应用程序都是C语言编写的。C语言的代码运行速度和汇编语言几乎一样。

1. Basic structure

  1. 预处理指令(包含指定的都文件), #include <stdio.h>
  2. 主函数int main(){},函数从这里开始执行

2. Compiling

gcc -o hello.out hello.c (默认生产a.out文件)

3. Tokens

c语言就是由一系列的令牌组成的:

  1. 分号: 每条语句必须以分号结束,跟perl一样
  2. 注释符: //单行注释;/* 单行或多行注释 */
  3. 标识符: 不允许有@,¥,%,区分大小写
  4. 关键字: 如break,do,if等
  5. 空格

4. Variables

4.1. Defile a variable:type variable_list;

告诉编译器在何处创建变量的存储,以及如何创建变量的存储。

4.2 Variable type

4.2.1 Integers
  1. char 1字节 char c, ch;
  2. int 2或4字节 int i, j, k;
  3. short 2字节
  4. long 4字节
  5. size_t >16 bit unsigned integer
  6. unassigned 没有负数
4.2.2 Float
  1. float 4字节
  2. double 8字节
  3. long double 16字节
4.2.3 void
  1. 函数返回为空:需要在定义函数的时候在函数前加void
  2. 函数参数为空:如int rand(void)
  3. 指针指向void:如内存分配函数void *malloc(size_t size)返回指向void的指针
4.2.4 引申类型
4.2.4.1 Array

存储一个相同类型元素的集合:

  1. 申明数组:type arrayName [arraySize]
  2. 初始化数组:double a[5] = {0.1, 3.4, 8.0, 1000.0, 5.2}
  3. 访问数组元素:索引从0开始
4.2.4.2 Enumeration
enum {
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
4.2.4.3 String

实际上是使用null字符’\0’终止的一维字符数组(char是整数类型):

  1. 创建字符串:
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char greeting[] = "Hello";
printf("Greeting message: %s\n", greeting );
  1. 字符串操作:
strcpy(s1, s2)	复制字符串 s2 到字符串 s1
strcat(s1, s2)	连接字符串 s2 到字符串 s1 的末尾
strlen(s1)	字符串 s1 的长度
strcmp(s1, s2)	s1=s2相同返回0;s1<s2返回小于0;s1>s2 返回大于0
strchr(s1, ch)	返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置
strstr(s1, s2)	返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置
4.2.4.4. Pointer:

其值为另一个变量的内存位置的直接地址,用*来指定变量为指针,在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值

  1. 创建和使用指针:
int main () {
int  var = 20;   /* 实际变量的声明 */
int  *ip;        /* 指针变量的声明 */
ip = &var;  /* 在指针变量中存储 var 的地址 */
printf("Address of var variable: %p\n", &var  );
/* 在指针变量中存储的地址 */
printf("Address stored in ip variable: %p\n", ip );
/* 使用指针访问值 */
printf("Value of *ip variable: %d\n", *ip );
return 0;
}
  1. 函数指针:
type (*fun_ptr)(type_of_args)
int max(int x, int y) {
	return x > y ? x : y;
}

int main(void) {
	/* p是函数指针 */
	int (*p)(int, int) = &max; // &可以省略
	d = p(p(a, b), c); 
	printf("最大的数字是: %d\n", d);
}
4.2.4.5. Struct:

允许存储不同类型的数据,表示一条记录,如想要跟踪图书馆中的书本属性,如title,author等等

  1. 定义结构体:
struct Books {
   	char  title[50];
   	char  author[50];
   	char  subject[100];
   	int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
 
int main() {
   printf("title : %s %d\n", book.title);
}
4.2.4.6. Union:

在相同的内存位置存储不同的数据类型,共用体可以有多个成员但任何时候只能有一个成员带有值。共用体占用的内存应足够存储共用体中最大的成员。

  1. 定义共用体
union Data {
		int i;
		float f;
		char  str[20];
		} data;
						
int main( ) {
		union Data data;        
 
		data.i = 10;
		data.f = 220.5;
		strcpy( data.str, "C Programming");
							
		return o;
}
4.2.4.7. Bit-fields:

存储布尔型变量,节省内存

  1. 定义位域:struct {type [member_name] : width ;};
struct {
		unsigned int age : 3;
} Age;

int main( ) {
		Age.age = 4;
		printf( "Age.age : %d\n", Age.age );
   		return 0;
}

4.3 Variable initialization:

type variable_name = value; 如int i=3, f=5;

5. Constant:

5.1. Define a constant:

  1. #define identifier value; 一般放在主函数外面,常量名一般都用大写
  2. const type variable = value; 在主函数内部定义

5.2. Type of constant:

5.2.1. Integers:
  1. 十进制 85
  2. 八进制(0开头) 0213
  3. 十六进制(0x或0X开头) 0x4b
  4. 无符号整数(u结尾) 30u
  5. 长整数(l结尾) 30l
  6. 无符号长整数 30ul
5.2.2. Float:
  1. 浮点类型 12.5f 等等
5.2.3. 字符常量:

\\, \n, \t等等

6. 存储类:

用来定义变量或函数的范围和生命周期

  1. auto类:所有局部变量(函数内部)默认的存储类,主能在函数内部使用
  2. register类:定义存储在寄存器中而不是内存中的局部变量,寄存器只用于需要快速访问的变量,比如计数器。
  3. static类:static的字面意思就是恒定,static定义的变量只初始化一次,在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。
  4. extern类:字面意思外部,用来在另一个文件中声明一个全局变量(函数外部)或函数。

7. Operator:

  1. 算术运算符: +,-,*,/,%,++,—-
  2. 关系运算符: >, <, >=, <=, ==, !=
  3. 逻辑运算符: &&, ||, !
  4. 位运算符: &, |, ^
  5. 赋值运算符: =, +=, -=, *=, /=, %=
  6. 三元操作符:?:
  7. \&返回变量的实际内存地址
  8. *返回一个指针指向变量

8. Logical statement:

8.1. 判断

8.2. Loop:

  1. 循环类型:
1. for循环:
		for (init; condition; increment) {
			    statements(s);
		} 
2. while循环:
		while (condition) {
				statement(s);
		}

8.3. Loop control:break,continue,

9. Function:

  1. 定义申明:告诉编译器函数的名称、返回类型和参数,return_type function(args), 注意如果想将函数的定义放在程序的最后面,那么在前面使用这个函数之前需要先申明它
  2. 函数定义:加上函数主体,return_type function(args) {body}

10. In and Out(c treats everything as file, including the hardware):

1. scanf()与printf():
			int main() {
    				float f;
    				printf("Enter a number: ");
    				// %f 匹配浮点型数据
    				scanf("%f",&f);
    				printf("Value = %f", f);
    				return 0;
			}
2. getchar():从屏幕读取下”一个”可用的字符,并把它返回为一个”整数”,不是字符串;
pitchar():把”一个”字符输出到屏幕上,并返回相同的字符。
			
			int main( ){
   				int c;
 
   				printf( "Enter a value :");
   				c = getchar( );
 
   				printf( "\nYou entered: ");
   				putchar( c );
   				printf( "\n");
   				return 0;
			}
3. gets(): 从stdin读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF;
		   puts(): 把字符串 s 和一个尾随的换行符写入到 stdout
			
			int main( ) {
				char str[100];
 
				printf( "Enter a value :");
				gets( str );
 
   				printf( "\nYou entered: ");
   				puts( str );
   				return 0;
			}

11. File IO:

11.1 Read in file:

int fputs(const char *s, FILE *fp);
int fprintf(FILE *fp,const char *format, ...) 
			
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);
}

11.2 读取文件:

char *fgets(char *buf, int n, FILE *fp); 从流中读取一个字符串, 把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。

int fscanf(FILE *fp, const char *format, …); 从文件中读取字符串,但在遇到第一个空格字符时停止读取。

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 );
}

12. Preprocessor:

以#开头,如#define(定义宏), #include(包含头文件), #undef(取消已定义的宏)等等。

预定义的宏: __DATE__; __TIME__; __FILE__; __LINE__; __STDC__

13. Head file:

拓展名为.h,包含了C函数声明和宏定义,被多个源文件中引用共享。建议把所有的常量、宏、系统全局变量和函数原型写在头文件中

语法:

#include <file> 	在系统目录的标准列表中搜索名为 file 的文件
#include "file"		在包含当前文件的目录中搜索名为 file 的文件

14. Error:

在发生错误时,大多数的C或UNIX函数调用返回1或 NULL,同时会设置一个错误代码errno,需要包含errno.h头文件

perror()函数:显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。

strerror()函数:返回一个指针,指针指向当前 errno 值的文本表示形式。

#include <stdio.h>
#include <errno.h>
#include <string.h>

extern int errno ;

int main () {
   	FILE * pf;
   	int errnum;
   	pf = fopen ("unexist.txt", "rb");
   	if (pf == NULL) {
      	errnum = errno;
      	fprintf(stderr, "错误号: %d\n", errno);
      	perror("通过 perror 输出错误");
      	fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
	} else {
      	fclose (pf);
   	}
   	return 0;
}

15. Memory management:

动态分配和管理的函数在头文件中

15.1. 内存管理函数:

  1. void *calloc(int num, int size); 在内存中动态地分配num个长度为size的连续空间,并将每一个字节都初始化为0
  2. void free(void *address); 释放 address 所指向的内存块,释放的是动态分配的内存空间
  3. void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据
  4. void *realloc(void *address, int newsize); 重新分配内存,把内存扩展到newsize

15.2 动态分配内存:

需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存

15.3. 重新调整内存的大小和释放内存:

在不需要内存时,应该调用函数free()来释放内存,或通过调用函数 realloc() 来增加或减少已分配的内存块的大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main() {
   		char name[100];
  		char *description;
 
   		strcpy(name, "Zara Ali");
 
   		/* 动态分配内存 */
   		description = (char *)malloc( 200 * sizeof(char) );
   		if( description == NULL ) {
      			fprintf(stderr, "Error - unable to allocate required memory\n");
   		}
   		else {
      			strcpy( description, "Zara ali a DPS student in class 10th");
   		}
   		/* 假设您想要存储更大的描述信息 */
   		description = realloc( description, 100 * sizeof(char) );
   		if( description == NULL ) {
      			fprintf(stderr, "Error - unable to allocate required memory\n");
   		}
   		else {
      			strcat( description, "She is in class 10th");
   		}
   		printf("Name = %s\n", name );
   		printf("Description: %s\n", description );
   		/* 使用 free() 函数释放内存 */
   		free(description);
}