1.指针 1. 指针 和指针变量 指针 :代表一个内存地址
指针变量 :存放指针的变量,指针变量的类型是存放的地址指向的变量的数据类型
变量名 :编译器会将变量名存放到一个符号表 中,每个符号对应一个地址。当调用变量时,按照符号找到对应的地址。然后进行操作。
char *pa = &a; printf ("%c" ,*pa);
避免访问未初始化的指针:可能会覆盖其他的数据
2. 指针和数组 数组名 是数组中第一个元素的地址
指向数组的指针 :将数组的首地址存放到指针变量中
1 2 3 int a[5 ] = {1 ,2 ,3 ,4 ,5 };char *p = &a[0 ];char *p = a;
指针的运算 :指针指向数组后,可以进行加减运算。相当于在数组中向前后移动n个元素。
1 2 3 4 int a[5 ] = {1 ,2 ,3 ,4 ,5 };int *p = a;printf ("%d\n" ,*p);printf ("%d\n" ,*(p+1 ));
使用指针定义和访问数组:
1 2 3 4 5 6 7 char *str = "Hello world" ; int i, length; length = strlen (str);for (i = 0 ; i < length; i++) { printf ("%c" ,str[i]); }
指针和数组名的区别 :
数组名是地址常量,而指针变量是变量,可以作为左值(lvalue)。而数组名不可以作为左值
1 2 3 4 5 6 7 char str[] = "Hello world" ;char *target = str; int count = 0 ;while (*target++ != '\0' ){ count++; }printf ("%d\n" ,count);
指针数组 和数组指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int *p1[5 ]; char *p[2 ] = { "Hello" , "World" };printf ("%c" ,*(p[0 ]+1 ));int (*p2)[5 ]; int a[5 ] = {1 ,2 ,3 ,4 ,5 };int *p = a;int a[5 ] = {1 ,2 ,3 ,4 ,5 };int (*p2)[5 ] = &a; printf ("%d\n" ,*(*p2+1 ));
二维数组
1 2 3 4 5 6 int array [4 ][5 ] = {0 };
二维数组 和数组指针
1 2 3 4 int array [2 ][3 ] = {{1 ,2 ,3 },{4 ,5 ,6 }};int (*p)[3 ] = array ;printf ("%d\n" ,**(p+1 ));printf ("%d\n" ,**(array +1 ));
3. void指针和NULL指针 void 是不确定类型,不可以用来申明变量
void指针 为通用指针,可以指向任意类型的数据。void指针不能解引用,编译器不能确定指向的数据类型,需要强制转换。
1 2 3 4 5 int num = 1 ;int *p = #void *a; a = p;printf ("%d\n" ,*(int *)a);
NULL指针 ,不指向任何的数据的空指针。在指针不知道初始化为什么地址时,可以初始化为NULL。NULL用于指针和对象,指向一个不被使用的地址。’\0’表示字符串的结尾。
1 2 3 4 int *p1;int *p2 = NULL ;printf ("%d\n" ,*p1); printf ("%d\n" ,*p2);
4.指向指针的指针 1 2 3 int num = 1' ;int *p = #int **pp = &p;
指针数组和指向指针的指针
1 2 3 4 5 6 char *p[2 ] = {"abc" ,"def" };char **pp1; pp1 = &p[1 ]; char **pp2[2 ]; pp2[0 ] = &p[0 ]; pp2[1 ] = &p[1 ];
常量和指针
1 2 const int num = 1 ; const int *p = #
常量指针
1 2 3 4 5 6 7 int num = 100 ;int * const p = # *p = 1024 ;const int * const p = &cnum; const int * const *pp = &p;
2.函数 函数的声明与定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int sum (int ) ; int sum (int n) { int res = 0 ; do { res += n; }while (n-- >0 ); return res; }int main () { int n; scanf ("%d" ,&n); printf ("%d\n" ,sum(n)); return 0 ; }
传值 和传址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <stdio.h> void swap (int , int ) ; void swap (int x, int y) { int temp; temp = x; x = y; y = temp; }int main () { int x = 3 , y = 5 ; swap(x,y); printf ("%d %d" ,x,y); return 0 ; }#include <stdio.h> void swap (int *, int *) ;void swap (int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; }int main () { int x = 3 , y = 5 ; swap(&x,&y); printf ("%d %d" ,x,y); return 0 ; }
可变参数 variable argument
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <stdarg.h> int sum (int n,...) ; int sum (int n,...) { int i, sum = 0 ; va_list vap; va_start(vap,n); for (i=0 ;i<n;i++){ sum += va_arg(vap, int ); } va_end(vap); return sum; }int main () { int x = 3 , y = 5 , z = 10 ; printf ("%d" ,sum(3 ,x,y,z)); return 0 ; }
指针函数 和函数指针
指针函数 :使用指针变量作为函数的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> char *getWord (char ) ; char *getWord (char c) { switch (c) { case 'A' : return "apple" ; case 'B' : return "banana" ; default : return "None" ; } }int main () { char input; scanf ("%c" ,&input); printf ("%s\n" ,getWord(input)); return 0 ; }
函数指针 :指向函数的指针。在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址 。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int sqt (int ) ;int sqt (int num) { return num * num; }int main () { int num; int (*fp)(int ); scanf ("%d" ,&num); fp = sqt; printf ("%d" ,(*fp)(num)); return 0 ; }
函数指针作为参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> int add (int ,int ) ;int sub (int ,int ) ;int calc (int (*fp)(int , int ),int ,int ) ;int add (int a, int b) { return a + b; }int sub (int a, int b) { return a -b; }int calc (int (*fp)(int , int ), int a, int b) { return (*fp)(a,b); }int main () { printf ("%d\n" ,calc(add,3 ,5 )); return 0 ; }
函数指针作为返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <stdio.h> int add (int ,int ) ;int sub (int ,int ) ;int calc (int (*fp)(int , int ),int ,int ) ;int (*select(char op))(int , int ); int add (int a, int b) { return a + b; }int sub (int a, int b) { return a -b; }int calc (int (*fp)(int , int ), int a, int b) { return (*fp)(a,b); }int (*select(char op))(int , int ){ switch (op) { case '+' : return add; case '-' : return sub; } }int main () { int a,b; char op; int (*fp)(int , int ); scanf ("%d%c%d" ,&a,&op,&b); fp = select(op); printf ("%d\n" ,calc(fp,a,b)); return 0 ; }
3. C语言细节 局部变量 和全局变量 :函数内部定义的函数是局部变量 ,外部定义的是全局变量
如果在函数内部存在与全局变量同名的局部变量,则在函数中屏蔽全局变量
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> void func () ;void func () { extern int count; count++; }int count = 0 ;int main () { func(); printf ("%d\n" ,count); return 0 ; }
使用大量的全局变量会占用更多的内存,会污染命名空间
作用域
代码块作用域 :形参和代码块中定义的变量只在代码块内部起作用
文件作用域 :从声明开始到文件结束均可以使用
原型作用域 :只适用于在函数原型中声明的参数名
函数作用域 :适用于goto语句的标签
链接属性 :static可以将external的变量表示为internal变量,显示某个变量只在本文件中被使用
external :多个文件中同名标识符表示一个实体
internal :单个文件中同名标识符表示一个实体
none :同名标识符表示不同实体
生存期
变量的存储类型 :指存储变量值的内存类型
默认存储类型,自动变量具有代码块作用域,自动存储期,空链接属性。默认可以不写。
寄存器存在于cpu的内部,cpu对寄存器中数据的读取和存储几乎没有任何延迟。与自动变量类似。无法获取寄存器变量的地址。
static声明的变量具有静态存储期,生存周期和全局变量一样,程序结束时释放。作用域仍然为代码块作用域。
变量在其他文件中已经定义过
typedef :为数据类型定义别名,在结构体中使用
1 2 3 4 5 6 7 8 9 10 11 12 typedef int interger;typedef struct Date { int year; int month; } DATE, *PDATE;typedef int (*PTR_TO_ARRAY) [3]; typedef int (*PTR_TO_FUN) (void ) ; typedef int *(*PTR_TO_FUN)(int );
动态内存管理
malloc :申请动态内存空间,位于内存的堆上,需要主动释放内存。
对内存空间进行操作:memset, memcpy, memmove, memcmp, memchr
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> int main (void ) { int *ptr; ptr = malloc (sizeof (int )); printf ("%p\n" ,ptr); free (ptr); return 0 ; }
内存泄漏 :申请的动态内存空间应该及时释放,否则会导致内存不足。或者丢失申请内存块的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> #include <stdlib.h> #include <string.h> int main (void ) { int *ptr = NULL ; int i; ptr = (int *)malloc (10 * sizeof (int )); memset (ptr, 0 , 10 *sizeof (int )); for (i=0 ; i < 10 ; i++){ printf ("%d " ,ptr[i]); } putchar ('\n' ); free (ptr); return 0 ; }int *ptr = (int *)calloc (8 , sizeof (int ));int *ptr = (int *)malloc (8 * sizeof (int ));memset (ptr, 0 , 8 * sizeof (int ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void *realloc (void *ptr, size_t size) ;#include <stdio.h> #include <stdlib.h> #include <string.h> int main (void ) { int *ptr1 = NULL ; int *ptr2 = NULL ; ptr1 = (int *)malloc (10 * sizeof (int )); ptr2 = (int *)malloc (20 * sizeof (int )); memcpy (ptr2, ptr1, 10 ); free (ptr1); free (ptr2); return 0 ; }#include <stdio.h> #include <stdlib.h> #include <string.h> int main (void ) { int i, num; int count = 0 ; int *ptr = NULL ; do { scanf ("%d" ,&num); count++; ptr = (int *)realloc (ptr,count * sizeof (int )); if (ptr == NULL ){ exit (1 ); } ptr[count-1 ] = num; } while (num != -1 ); for (i = 0 ; i < count; i++){ printf ("%d\n" ,ptr[i]); } free (ptr); return 0 ; }
内存管理
malloc 分配内存会导致大量内存碎片的产生,同时有时间上的消耗
内存池 :程序额外维护的一个缓存区域。用户申请内存块的时候,优先在内存池中查找合适的空间。释放内存时,优先释放到内存池中。
c语言的内存布局规律
内存地址从低至高排列:
代码段:存放程序执行代码的一部分,如函数
数据段:存放已初始化的全局变量和局部静态变量
BSS段:存放未初始化的全局变量和局部静态变量
堆:存放动态分配的内存段,手动申请。堆中的数据可以由不同的函数访问到。从低地址向高地址发展。
栈:存放局部变量,参数和返回值等,系统自动分配。由高地址向低地址发展。
c语言的预处理(#和##)
1 2 3 4 5 6 7 8 9 10 11 #define PI 3.14 #define S PI * 25 #undef #define max(x,y) (((x) > (y))?(x) : (y)) #define STR(s) # s #define TEST(x,y) x ## y
内联函数 :解决程序中函数调用的效率问题。减少了函数调用的时间消耗,但是增加了代码编译的时间。
4. 结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 struct Book { char title[128 ]; char author[40 ]; float price; unsigned int date; char publisher[40 ]; };struct Book book ;struct Book book = { .price = 10.8 , .date = 20170912 };struct Date { int year; int month; int day; };struct Book { char title[128 ]; float price; struct Date date ; };
结构体数组 :数组元素为结构体,而不是基本的数据结构
1 2 3 4 5 6 7 8 struct Date { int year; int month; int day; } date[10 ]; struct Date date [10];
结构体指针
1 2 3 4 5 6 struct date *pt ; pt = &date; (*pt).year; pt->year;
传递结构体变量
1 2 struct 结构体1 函数名( struct 结构体2 结构体2变量);
传递指向结构体变量的指针
1 2 struct 结构体1 *函数名( struct 结构体2 *结构体2变量);
动态申请结构体
1 2 struct Book *b1 ; b1 = (struct Book *)malloc (sizeof (struct Book));
单链表
1 2 3 4 5 6 7 struct Test { int x; int y; struct Test *next ; };
5. 共用体 共用体,或者称为联合体或联合类型。共用体的所有成员同享一个内存地址,只能使用某一个成员。否则成员的值之间会互相覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <string.h> union Test { int i; double pi; char str[6 ]; };int main (void ) { union Test test ; test.i = 10 ; test.pi = 1.1 ; strcpy (test.str, "Hello" ); printf ("%p\n" ,&test.i); printf ("%p\n" ,&test.pi); printf ("%p\n" ,&test.str); return 0 ; }
6. 枚举类型 1 2 enum Week { sun, mom, tue, wed, thu, fri, sat}; enum Week today ;
(1) 枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。
(2) Week是一个标识符,可以看成这个集合的名字,是一个可选项 ,即是可有可无的项。
(3) 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
(4) 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
(5) 枚举型是预处理指令#define的替代。
(6) 类型定义以分号**;**结束。
7. 位 位域 :能够将一个字节拆分使用,位域是字节的一部分
1 2 3 4 5 6 7 struct Test { unsigned int a:1 ; unsigned int b:1 ; unsigned int c:5 ; unsigned int :10 ; };
逻辑位运算符 :对位进行运算。用于掩码,打开位,关闭位,转置位等
移位运算符 :将变量的二进制位进行左移或右移
1 2 3 4 5 6 7 8 9 10 11 12 int main (void ) { int value = 1 ; while (value < 1024 ){ value <<= 1 ; printf ("%d\n" ,value); } return 0 ; }
8. 文件操作
1 2 3 4 5 6 7 8 9 10 11 fgetc getc fputc putc fgetsfputs fscanf fprintf