C语言提高笔记

光光

发布日期: 2019-07-30 16:00:39 浏览量: 263
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

C提高

一 基本概念强化

1、头文件函数声明

分文件时,头文件防止头文件重复包含

  1. #pragma once
  2. //兼容C++编译器
  3. //如果是C++编译器,按照C标准编译
  4. #ifdef __cplusplus
  5. extern "c"
  6. {
  7. #endif
  8. //
  9. #ifdef __cplusplus
  10. }
  11. #endif

数组作为函数参数会退化为一级指针:

  • 数组做函数参数时,应该把数组元素个数也传递给函数;

  • 形参中的数组,编译器把它当做指针处理,这是C语言的特色;

  • 实参中的数组,和形参中数组本质不一样;

  1. void print_array(int a[], int n)

数据类型的本质:是固定内存大小的别名

数据类型的作用:编译器预算对象(变量)分配的内存空间大小

  1. int a;//告诉C编译器分配4个字节的内存
  2. 数据类型可以通过typedef起别名:
  3. typedef unsigned int u32;
  4. typedef struct student
  5. {
  6. int a;
  7. char b;
  8. }STU;
  9. 可以通过sizeof()测类型大小;

Void类型 (无类型)

  • 如果函数没有参数,定义函数时,可以用void修饰:int fun(void);

  • 如果函数没有返回值,必须用void修饰:void fun(int a);

  • 不能定义void类型的普通变量,void a;//err,无法确定类型,不同类型分配空间不一样

  • 可以定义void 变量:void p;//ok,32位永远是4个字节,64位8字节

  • void *p;万用指针,函数返回值,函数参数

变量的本质:内存空间的别名

必须通过数据类型定义变量

变量相当于门牌号,内存相当于房间,通过门牌号找到房间,通过变量找到所对应的内存

变量的赋值:1. 直接赋值 2. 间接赋值

  1. int a;
  2. a=100;//直接赋值
  3. int *p=0;
  4. p=&a;//指针指向谁,就把谁的地址赋值给指针
  5. *p=22;//间接赋值

重点:没有变量,哪来内存,没有内存,哪里来内存首地址

变量三要素(名称、大小、作用域),变量的生命周期

内存四区模型

  • 栈区:系统分配空间,系统自动回收,函数内部定义的变量,函数参数,函数结束,其内部变量生命周期结束;

  • 堆区:程序员动态分配空间,由程序员手动释放,没有手动释放,分配的空间一直可用;

  • 静态区(全局区):(包括全局变量和静态变量,里面又分为初始化区和未初始化区,文字常量区:字符常量):整个程序运行完毕,系统自动回收;

  • 代码区,内存四区模型图

    • 栈区地址生长方向:地址由上往下递减;
    • 堆区地址生长方向:地址由下往上递增;
    • 数组buf,buf+1地址永远递增。

函数调用模型

程序各个函数运行流程(压栈弹栈,入栈出栈,先进后出)

二 指针强化

指针也是一种数据类型,指针变量也是一种变量,和int a本质是一样的

  1. 1)指针变量也是一种变量,也有空间,32位程序大小为4个字节
  2. int *p = 0x1122;
  3. 2)*操作符,*相当于钥匙,通过*可以找到指针所指向的内存区域
  4. int a = 10;
  5. int *p = NULL;
  6. p = &a; //指针指向谁,就把谁的地址赋值给指针
  7. *p = 22; //*放=左边,给内存赋值,写内存
  8. int b = *p; //*放=右边,取内存的值,读内存
  9. 3)指针变量,和指向指向的内存是两个不同的概念
  10. char *p = NULL;
  11. char buf[] = "abcdef";
  12. //改变指针变量的值
  13. p = buf;
  14. p = p + 1; //改变了指向变量的值,改变了指针的指向
  15. *p = 'm'; //改变指针指向的内存,并不会影响到指针的值
  16. 4)写内存时,一定要确保内存可写
  17. char *buf2 = "sadgkdsjlgjlsdk"; //文字常量区,内存不可改
  18. //buf2[2] = '1'; //err

间接赋值(*p)是指针存在最大意义

  1. 1)间接赋值三大条件
  2. a) 两个变量
  3. b) 建立关系
  4. c) 通过 * 操作符进行间接赋值
  5. 1)
  6. int a;
  7. int *p; //两个变量
  8. p = &a; //建立关系
  9. *p = 100; //通过 * 操作符进行间接赋值
  10. 2)
  11. int b;
  12. fun(&b); //两个变量之一:实参,给函数传参时,相当于建立关系
  13. //p = &b
  14. void fun(int *p) //两个变量之一:形参参
  15. {
  16. *p = 100; //通过 * 操作符进行间接赋值
  17. }
  18. 2)如何定义合适类型的指针变量
  19. //某个变量的地址需要定义一个怎么样类型的变量保存
  20. //在这个类型的基础上加一个*
  21. int b;
  22. int *q = &b;
  23. int **t = &q;
  1. 重要:如果想通过函数形参改变实参的值,必须传地址
  2. 1、值传递,形参的任何修改不会影响到实参
  3. 2、地址传递,形参(通过*操作符号)的任何修改会影响到实参
  4. 1级指针形参,去间接修改了0级指针(实参)的值。
  5. 2级指针形参,去间接修改了1级指针(实参)的值。
  6. 3级指针形参,去间接修改了2级指针(实参)的值。
  7. n级指针形参,去间接修改了n-1级指针(实参)的值。
  1. int a = 10;
  2. fun(a); //值传递
  3. void fun(int b)
  4. {
  5. b = 20;
  6. }
  7. fun2(&a);//地址传递
  8. void fun2(int *p)
  9. {
  10. *p = 20; //通过*操作内存
  11. }
  12. int *p = 0x1122;
  13. void fun3(p);//值传递
  14. void fun3(int *p)
  15. {
  16. p = 0x2233;
  17. }
  18. void fun4(&p);//地址传递
  19. void fun4(int **p)
  20. {
  21. *p = 0xaabb; //通过*操作内存
  22. }

3、不允许向NULL和未知非法地址拷贝内存

  1. char *p = NULL;
  2. //给p指向的内存区域拷贝内容
  3. strcpy(q, "1234"); //err
  4. //静态
  5. char buf[100];
  6. p = buf;
  7. strcpy(q, "1234"); //ok
  8. //动态
  9. p = (char *)malloc(sizeof(char) * 10 );
  10. strcpy(q, "1234"); //ok
  11. char *q = "123456";
  12. strcpy(q, "abcd"); //?

4、void *指针的使用

  1. void *p = 0x1122; //可以这么做,不建议,一般赋值为NULL
  2. char buf[1024] = "abcd";
  3. p = (void *)buf; //指向 char
  4. printf("p = %s\n", (char *)p); //使用时转化为实际类型指针
  5. int a[100] = { 1, 2, 3, 4 };
  6. p = (void *)a; //指向 int
  7. int i = 0;
  8. for (i = 0; i < 4; i++)
  9. {
  10. //使用时转化为实际类型指针
  11. int *tmp = (int *)p;
  12. printf("%d ", *(tmp+i));
  13. printf("%d ", tmp[i]);
  14. printf("%d ", *( (int *)p + i ) );
  15. }
  16. void * 常用于函数参数:memset(), memcpy()

5、栈区返回变量的值和变量的地址区别

  1. int fun()
  2. {
  3. int a = 10;
  4. return a;
  5. }
  6. int *fun2()
  7. {
  8. int a = 10;
  9. return &a;
  10. }
  11. int *fun3()
  12. {
  13. static int a = 10;
  14. return &a;
  15. }
  16. int b = fun(); //ok, b的值为10
  17. //也ok, p确实也保存了fun2()内部a的地址
  18. //但是,fun2完毕,a就释放,p就指向未知区域
  19. int *p = fun2();
  20. //ok,fun3()中的a在全局区,函数运行完毕,a的空间不释放
  21. int *q = fun3();

6、.c -> 可执行程序过程
预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
编译:检查语法,将预处理后文件编译生成汇编文件
汇编:将汇编文件生成目标文件(二进制文件)
链接:将目标文件链接为可执行程序
程序只有在运行才加载到内存(由系统完成),但是某个变量具体分配多大,是在编译阶段就已经确定了,换句话说,在编译阶段做完处理后,程序运行时系统才知道分配多大的空间,所以,很多时候说,这个变量的空间在编译时就分配(确定)了。

7、指针做函数参数的输入输出特性
输入:主调函数分配内存
输出:被调用函数分配内存
//结合内存四区模型图

  1. main()
  2. {
  3. char buf[100] = "123456";
  4. fun1(buf); //输入
  5. char *p = NULL;
  6. fun2(&p); //输出
  7. //因为p在fun2()动态分配空间了,使用完毕应该释放
  8. if(p != NULL)
  9. {
  10. free(p);
  11. p = NULL;
  12. }
  13. }
  14. void fun1(char *p /*in*/)
  15. {
  16. strcpy(p, "1234")
  17. }
  18. void fun2(char **p /*out*/)
  19. {
  20. char *tmp = malloc(100);
  21. strcpy(tmp, "1234");
  22. *p = tmp; //间接赋值是指针存在最大意义,通过*操作内存
  23. }

8、变量内存的值和变量的地址
int a = 0;
a变量内存的值为0
a变量的地址(&a)绝对不为0,只要定义了变量,系统会自动为其分配空间(一个合法不为0的地址)

三 字符串操作

1、字符串基本操作
1)字符串初始化
/ C语言没有字符串类型,用字符数组模拟
C语言字符串以数字0,或字符 ‘\0’ 结尾,数字 0 和字符 ‘\0’ 等价
/
char str1[100] = { ‘a’, ‘b’, ‘c’, ‘d’ }; //没赋值的自动以数字0填充
char str2[] = { ‘a’, ‘b’, ‘c’, ‘d’ }; //数组长度为4,结尾没有数字0
char str4[] = “abcdef”; //常用赋值方式,栈区
char p = “abcdef”; //文字常量区,内容不允许被修改
char
buf = (char *)malloc(100); //堆区
strcpy(buf, “abcd”); //“abcd”拷贝到buf指向的内存区域中

2)sizeof和strlen区别
//使用字符串初始化,常用
char buf8[] = “abc”;
//strlen: 测字符串长度,不包含数字0,字符’\0’
//sizeof:测数组长度,包含数字0,字符’\0’
printf(“strlen = %d, sizeof = %d\n”, strlen(buf8), sizeof(buf8)); 3 4
char buf9[100] = “abc”;
printf(“strlen = %d, sizeof = %d\n”, strlen(buf9), sizeof(buf9)); 3 100

3)’\0’ 后面最好别跟数字,因为几个数字合起来有可能是一个转义字符
//\012相当于\n
char str[] = “\0129”;
printf(“%s aaaa\n”, str);

4)字符’\0’, 数字0, 字符’0’, NULL的区别
a) 字符’\0’ ASCII码值为 0 的字符
字符’\0’ 和 数字 0 是等级的,’\0’中’\’是转义字符
char buf[100];
//下面是等级,在数组第10个位置设置字符串结束符
buf[10] = 0;
buf[10] = ‘\0’;
b) 字符’0’是字符串的某个字符内容为’0’, ASCII码值为 48 的字符
char buf[100];
buf[0] = ‘0’; //第0个字符为字符 ‘0’
c) NULL 标准头文件(stdio.h)的宏 其值为数字 0

5)数组法、指针法操作字符串
char buf[] = “abdgdgdsg”
char p = buf; //buf是数组首元素地址,它也是指针
for (i = 0; i < strlen(buf); i++)
{
//[ ] 和
操作是等价的,也是操作指针指向内存
printf(“%c “, buf[i]); //符合程序员习惯
printf(“%c “, p[i]); //符合程序员习惯
printf(“%c “, (p+i));
printf(“%c “,
(buf + i));
}
注意:数组名也是指针,数组首元素地址,但是,它是一个只读常量
p++; //ok
buf++; //err

6)字符串拷贝函数
//成功为0,失败非0
//1 判断形参指针是否为NULL
//2 最好不要直接使用形参
int my_strcpy(char dst, char src)
{
if (dst == NULL || src == NULL)
{
return -1;
}
//辅助变量把形参接过来
char to = dst;
char
from = src;

  1. //*dst = *src
  2. //dst++, src++
  3. //判断 *dst是否为0, 为0跳出循环
  4. while (*to++ = *from++) ;
  5. return 0;
  6. }

2、项目开发常用字符串应用模型
1、利用strstr标准库函数找出一个字符串中substr出现的个数
1)do-while模型:
char p = “11abcd111122abcd333abcd3322abcd3333322qqq”;
int n = 0;
do {
p = strstr(p, “abcd”);
if (p != NULL)
{
n++; //累计个数
//重新设置查找的起点
p = p + strlen(“abcd”);
}
else //如果没有匹配的字符串,跳出循环
{
break;
}
} while (
p != 0); //如果没有到结尾

2)while模型:
char p = “11abcd22222abcd33333abcd444444qqq”;
int n = 0;
while( (p = strstr(p, “abcd”)) != NULL )
{
//能进来,肯定有匹配的子串
//重新设置起点位置
p = p + strlen(“abcd”);
n++;
if(
p == 0)//如果到结束符
{
break;
}
}
printf(“n = %d\n”,n);

3)函数封装实现
int my_strstr(char p,int n)
{
//辅助变量
int i = 0;
char tmp = p;
while((tmp = strstr(tmp, “abcd”)) != NULL)
{
//能进来,肯定有匹配的子串
//重新设置起点位置
tmp = tmp + strlen(“abcd”);
i++;
if(
tmp == 0)//如果到结束符
{
break;
}
}
//间接赋值
n = i;
return 0;
}
int main(void)
{
char
p = “11abcd22222abcd33333abcd444444qqq”;
int n = 0;
int ret = 0;
ret = my_strstr(p,&n);
if(ret != 0)
{
return ret;
}
printf(“n = %d\n”,n);
return 0;
}

2、两头堵模型
char *p = “ abcddsgadsgefg “;
int begin = 0;
int end = strlen(p) - 1;
int n = 0;
if(end < 0){
return;
}
//从左往右移动,如果当前字符为空,而且没有结束
while (p[begin] == ‘ ‘ && p[begin] != 0)
{
begin++; //位置从右移动一位
}
//从右往左移动,如果当前字符为空
while (p[end] == ‘ ‘)
{
end—; //往左移动
}
n = end - begin + 1; //非空元素个数
strncpy(buf, p + begin, n);
buf[n] = 0;

  1. //如何证明strncpy()拷贝不会自动加字符串结束符'\0'
  2. char dst[] = "aaaaaaaaaaaaaaa";
  3. strncpy(dst, "123", 3);
  4. printf("dst = %s\n", dst); //dst = "123aaaaaaaaaaaa"

四 const的使用

1)const声明变量为只读
//const修饰的变量,定义时初始化
const int a = 10;
//a = 100; //error
int q = &a; q = 22;
char buf[100] = “abcdef”;
//从左往右看,跳过类型,看修饰那个字符
//如果是修饰,说明指针指向的内存不能改变
//如果是修饰指针变量,说明指针的指向不能改变,指针的值不能修改
const char
p = buf; //类似于文字常量区 char p = “123445”;
char const
p = buf; //修饰,指针指向能变,指针指向的内存不能变
//p[0] = ‘1’; //error
p = “123456”; //ok
char
const p1 = buf; //修饰指针变量,指针指向的内存,指针指向不能变
//p1 = “123456”; //error
p1[0] = ‘1’; //ok
const char * const p2 = buf; //p2, 只读

2)如何引用另外.c中的const变量
extern const int a;不能再赋值,只能声明

五 多级指针

1)如何定义合适类型的指针变量
//某个变量的地址需要定义一个怎么样类型的变量保存
//在这个类型的基础上加一个
int b;
int
q = &b; //一级指针
int t = &q; //二级指针
int *
m = &t; //三级指针

2)二级指针做输出
输入:主调函数分配内存
输出:被调用函数分配内存
char *p1 = NULL; //没有分配内存
int len = 0;
getMem(&p1, &len); //要想通过函数的形参改变实参的值,必须地址传递

  1. void getMem(char **p1 /*out*/, int *plen /*in*/)
  2. {
  3. //间接赋值,是指针存在最大的意义。
  4. *p1 = malloc(100);
  5. *plen = 100;
  6. }
  7. 指针做参数输出特性

3)二级指针做输入的三种内存模型
1、//指针数组,数组的每个元素都是指针类型
// [] 的优先级比 高,它是数组,每个元素都是指针类型(char
char myArray[] = {“aaaaaa”, “ccccc”, “bbbbbb”, “111111”};
//char **p = {“aaaaaa”, “ccccc”, “bbbbbb”, “111111”}; //err
void fun(int a[]);
void fun(int
a); // a[] 等价于 a
void printMyArray(char
myArray[], int num);
// char 代表类型,myArray[]等价于 myArray
// char myArray[] -> char myArray
void printMyArray(char
myArray, int num);
void sortMyArray(char *
myArray, int num);
如果排序,交换的是指针的指向,因为原来指针指向是文字常量区,文字常量区的内存一旦分配,内存就不能变。

2、//二维数组 10行30列,10个一维数组a[30]
//总共能容量10行字符串,这个用了 4 行
//每行字符串长度不能超过29,留一个位置放结束符:数字0
char myArray[10][30] = {“aaaaaa”, “ccccc”, “bbbbbbb”, “1111111111111”};
void printMyArray(char myArray[10][30], int num);
void sortMyArray(char myArray[10][30], int num);
//定义二维数组,不写第一个[ ]值有条件,必须要初始化
char a[][30] = {“aaaaaa”, “ccccc”, “bbbbbbb”, “1111111111111”};//ok
char a[][30]; //err,定义时必须初始化

  1. 二维数组的数组名代表首行地址(第一行一维数组的地址)
  2. 首行地址和首行首元素地址的值是一样的,但是它们步长不一样
  3. 首行地址+1,跳过一行,一行30个字节,+30
  4. 首行首元素地址+1,跳过一个字符,一个字符为1个字节,+1
  5. sizeof(a): 4个一维数组,每个数组长度为304 * 30 = 120
  6. sizeof(a[0]): 0个一维数组首元素地址,相当于测第0个一维数组的长度:为30
  7. char b[30];
  8. &b代表整个一维数组的地址,相当于二维数组首行地址
  9. b代表一维数组首元素地址,相当于二维数组首行首元素地址
  10. &b b 的值虽然是一样,但是,它们的步长不一样
  11. &b + 1 跳过整个数组,+30
  12. b+1: 跳过1个字符,+1
  13. //不能通过 char ** 作为函数形参,因为指针+1步长不一样
  14. // char **,指针+1步长为 4 个字节
  15. // char a[][30],指针+1步长为 1 行的长度,这里为 30 个字节
  16. void printMyArray(char **buf, int num);
  17. 3int a[3];
  18. int *q = (int *)malloc(3 * sizeof(int)); //相当于q[3]
  19. //动态分配一个数组,每个元素都是char * //char *buf[3]
  20. int n = 3;
  21. char **buf = (char **)malloc(n * sizeof(char *)); //相当于 char *buf[3]
  22. if (buf = = NULL)
  23. {
  24. return -1;

}
for (i = 0; i < n; i++)
{
buf[i] = (char )malloc(30 sizeof(char));
}

  1. char **myArray = NULL;
  2. char **getMem(int num); //手工打造二维数组
  3. void printMyArray(char **myArray, int num);
  4. void sortMyArray(char **myArray, int num);
  5. void arrayFree(char **myArray, int num);

第三种内存模型:

  1. char **getMem(int n)
  2. {
  3. int i = 0;
  4. char **buf = (char **)malloc(n * sizeof(char *)); //char *buf[3]
  5. if (buf == NULL)
  6. {
  7. return NULL;
  8. }
  9. for (i = 0; i < n; i++)
  10. {
  11. buf[i] = (char *)malloc(30 * sizeof(char));
  12. char str[30];
  13. sprintf(str, "test%d%d", i, i);
  14. strcpy(buf[i], str);
  15. }
  16. return buf;
  17. }
  18. void print_buf(char **buf, int n)
  19. {
  20. int i = 0;
  21. for (i = 0; i < n; i++)
  22. {
  23. printf("%s, ", buf[i]);
  24. }
  25. printf("\n");
  26. }
  27. void free_buf(char **buf, int n)
  28. {
  29. int i = 0;
  30. for (i = 0; i < n; i++)
  31. {
  32. free(buf[i]);
  33. buf[i] = NULL;
  34. }
  35. if (buf != NULL)
  36. {
  37. free(buf);
  38. buf = NULL;
  39. }
  40. }
  41. int main(void)
  42. {
  43. char **buf = NULL;
  44. int n = 3;
  45. buf = getMem(n);
  46. if (buf == NULL)
  47. {
  48. printf("getMem err\n");
  49. return -1;
  50. }
  51. print_buf(buf, n);
  52. free_buf(buf, n);
  53. buf = NULL;
  54. printf("\n");
  55. system("pause");
  56. return 0;
  57. }

1、一维数组的初始化
int a[] = { 1, 3, 5 }; //3个元素
int b[5] = { 1, 2, 3 }; //a[3], a[4]自动初始化为0
int c[10] = { 0 }; //全部元素初始化为0
memset(c, 0, sizeof(c)); //通过memset给数组每个元素赋值为0

2、数组类型
int a[] = { 1, 3, 5 }; //3个元素
a: 数组首行首元素地址,一级指针
&a: 整个数组的首地址,二级指针

  1. 首行首元素地址和首行(整个一维数组)地址值虽然是一样,但是它们的步长不一样
  2. a+1: 跳过1元素,一元素为4字节,步长4个字节
  3. &a+1: 跳过整个数组,整个数组长度为 3*4 = 12,步长 3 * 4 = 12
  4. sizeof(a): 传参为:数组首行首元素地址,测数组(int [3])的长度,3 * 4 = 12
  5. sizeof(a[0]): 传参为:数组首元素(不是地址),每个元素为int类, 4字节
  6. sizeof(&a):传参为:一维数组整个数组的地址(首行地址),编译器当做指针类型,4字节
  7. (重要)首行地址 --> 首行首元素地址(加*)
  8. &a:首行地址
  9. *&a -> a : 首行首元素地址
  10. //数组也是一种数据类型,类型本质:固定大小内存块别名
  11. //由元素类型和内存大小(元素个数)共同决定 int a[5] int[5]
  12. //可以通过typedef定义数组类型
  13. //有typedef是类型,没有typedef是变量
  14. typedef int ARRARY[5]; //定义了一个名字为ARRARY的数组类型
  15. //等价于typedef int (ARRARY)[5];
  16. //根据数组类型,定义变量
  17. //ARRARY的位置替代为d,去掉typedef,int d[5]
  18. ARRARY d; //相当于int d[5];

3、指针数组(它是数组,每个元素都是指针)
1)指针数组的定义
//指针数组变量
//[]优先级比高,它是数组,每个元素都是指针(char
char *str[] = { “111”, “2222222” };

  1. char **str = { "111", "2222222" }; //err
  2. 2)指针数组做形参
  3. void fun(char *str[]);
  4. void fun(char **str); //str[] -> *str
  5. 3main函数的指针数组
  6. //argc: 传参数的个数(包含可执行程序)
  7. //argv:指针数组,指向输入的参数
  8. int main(int argc, char *argv[]);
  9. : demo.exe a b test
  10. int argc = 4
  11. char *argv[] = {"demo.exe", "a", "b", "test"}

4、数组指针变量(它是指针变量,指向数组的指针)
//定义数组变量
int a[10];
//有typedef:类型
//没有typedef:变量
1、根据数组类型,定义指针变量,数组指针变量
typedef int ARRARY[10]; //定义了一个名字为ARRARY的数组类型
//等价于typedef int (ARRARY)[10];

  1. ARRARY *p; //数组指针变量
  2. //编译会有警告,但不会出错,因为 a 和 &a的值一样
  3. //就算p = a这样赋值,编译器内部也会自动转换为 p = &a
  4. //不建议这么做
  5. p = a;
  6. //p 指向a数组,指向一维数组的指针
  7. p = &a;
  8. //如何操作数组指针变量 p
  9. int i = 0;
  10. for (i = 0; i < 10; i++)
  11. {
  12. (*p)[i] = i + 1;
  13. //p = &a
  14. //*p -> *(&a) -> a
  15. //(*p)[i] -> a[i]
  16. }
  17. 2、直接定义数组指针变量(常用)
  18. //()[]同级,从左往右看
  19. //()有*,它是一个指针,[]代表数组
  20. //指向数组的指针变量,[]中的数字代表指针+1的步长
  21. int(*p)[10];
  22. //p 指向a数组,指向一维数组的指针
  23. p = &a;
  24. 3、先定义数组指针类型,再根据类型定义指针变量(常用)
  25. //和指针数组写法很类似,多了()
  26. //()和[]优先级一样,从左往右
  27. //()有指针,它是一个指针,[]
  28. //指向数组的指针,它有typedef,所有它是一个数组指针类型
  29. //数组指针类型,加上typedef
  30. typedef int(*Q)[10];
  31. Q p; //根据类型定义变量,p是数组指针变量
  32. p = &a; //p指向数组a

5、多维数组本质
1)二维数组初始化
int a1[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int a2[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
int a3[][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

  1. 2)内存中并不存在多维数组,多维数组在内存中都是线性存储
  2. int a[3][5] = { 0 };
  3. int *b = (int *)a;
  4. int i = 0;
  5. for(i = 0; i < 15; i++)
  6. {
  7. printf("%d ", b[i]);
  8. }
  9. printfA((int *)a,sizeof(a)/sizeof(a[0][0]));
  10. 3)多维数组名
  11. //学会类比
  12. int b[5] = {0};
  13. b: 首行首元素地址, +1,跳 4 个字节
  14. &b:首行地址,+1,跳 4*5 = 20个字节
  15. //二维数组实际上就是 N 个一维数组
  16. //把二维数组第一个[]的值看做标志位,0 -> 2
  17. //第0个一维数组a[5] -> 第2个一维数组a[5]
  18. int a[3][5] = { 0 };
  19. a:二维数组首元素地址
  20. 代表首行地址,相当于一维数组整个数组的地址,相当于上面的 &b,本来就是一个二级指针
  21. //(重要)首行地址 --> 首行首元素地址(加*)
  22. *a:首行首元素地址,相当于一维数组首元素地址,相当于上面的 b
  23. a + i -> &a[i]: i行地址
  24. //(重要)某行地址 --> 某行首元素地址(加*)
  25. *(a+i) -> *&a[i] -> a[i]: i行首元素地址
  26. //第i行j列元素的地址,某行首元素地址 + 偏移量
  27. *(a+i)+j -> a[i]+j -> &a[i][j]: ij列元素的地址
  28. //第i行j列元素的值,第i行j列元素的地址的基础上(加 *)
  29. *(*(a+i)+j) -> a[i][j]: ij列元素的值
  30. int a[3][5] = { 0 };
  31. sizeof(a): 二维数组整个数组长度,4 * 3 * 5 = 60
  32. sizeof(a[0]):a[0]为第0行首元素地址,相当于测第0行一维数组的长度:4 * 5 = 20
  33. sizeof(a[0][0]):a[0][0]为第00列元素(是元素,不是地址),测某个元素长度:4字节
  34. 4)多维数组名,实际上是一个数组指针,指向数组的指针,步长为一行字节长度
  35. int a[3][5] = { 0 };
  36. //定义一个数组指针类型的变量
  37. int(*p)[5];
  38. //编译会有警告,但不会出错,因为 a 和 &a的值一样
  39. //但是&a代表整个二维数组的首地址
  40. //就算p = &a这样赋值,编译器内部也会自动转换为 p = a
  41. //不建议这么做
  42. p = &a;
  43. //a 本来就是第0个一维数组整个数组的地址,所以,无需加&
  44. p = a;
  45. 5)二维数组做形参的三种形式
  46. //一维数组做函数参数退化为一级指针
  47. //二维数组(多维数组)做函数参数,退化为数组指针
  48. int a[3][5] = { 0 };
  49. void print_array1(int a[3][5]);
  50. //第一维的数组,可以不写
  51. //第二维必须写,代表步长,确定指针+1的步长 5*4
  52. void print_array2(int a[][5])
  53. //形参为数组指针变量,[]的数字代表步长
  54. void print_array3(int (*a)[5]);
  55. //a+1和二维数组的步长不一样
  56. //这里的步长为4
  57. //上面二维数组的步长为 5 * 4 = 20
  58. void print_array3(int **a); //err

6、小结
typedef int A[10];//A:数组类型
A b;//int b[10],数组类型变量,普通变量
A *p;//数组类型定义数组指针变量

  1. typedef int (*P)[10];//数组指针类型
  2. P p;//数组指针变量
  3. int (*q)[10];//数组指针变量

六 结构体

1、结构体类型基本操作
1)结构体类型定义
//struct结构体关键字
//struct STU合起来才是类型名
//{}后面有个分号
struct Stu
{
char name[32];
char tile[32];
int age;
char addr[50];
};
//通过typedef把struct Stu改名为Stu
typedef struct Stu
{
int a;
}Stu;

  1. 2)结构体变量的定义
  2. //1)先定义类型,再定义变量,最常用
  3. struct Stu a;//全局变量、局部变量
  4. //2)定义类型的同时,定义变量
  5. struct _Stu
  6. {
  7. char name[32];
  8. char tile[32];
  9. int age;
  10. char addr[50];
  11. }c;
  12. struct
  13. {
  14. char name[32];
  15. char tile[32];
  16. int age;
  17. char addr[50];
  18. }e, f;
  19. 3)结构体变量初始化
  20. //定义变量同时时初始化,通过{}
  21. struct Stu g = { "lily", "teacher", 22, "guangzhou" };
  22. 4)变量和指针法操作结构体成员
  23. //变量法, 点运算符
  24. struct Stu h;
  25. strcpy(h.name, "^_^");
  26. (&h)->name
  27. //指针法, ->
  28. //结构体指针变量,没有指向空间,不能给其成员赋值
  29. struct Stu *p;
  30. p = &h;
  31. strcpy(p->name, "abc");
  32. (*p).name
  33. 结构体也是一种数据类型,复合类型,自定义类型

5)结构体数组

  1. //结构体类型
  2. typedef struct Teacher
  3. {
  4. char name[32];
  5. int age;
  6. }Teacher;
  7. //定义结构体数组,同时初始化
  8. Teacher t1[2] =
  9. {
  10. { "lily", 18 },
  11. { "lilei", 22 }
  12. };
  13. //静态数组
  14. Teacher t1[2] = {"lily", 18, "lilei", 22 };
  15. int i = 0;
  16. for(i = 0; i < 2; i++)
  17. {
  18. printf(“%s, %d\n”, t1[i].name, t1[i].age);
  19. }
  20. //动态数组
  21. Teacher *p = (Teacher *)malloc(3 * sizeof(Teacher));
  22. if(p ==NULL)
  23. {
  24. return -1;
  25. }
  26. char buf[50];
  27. int i;
  28. for(i = 0; i < 3; i++)
  29. {
  30. sprintf(buf,"name%d%d%d",i,i,i);
  31. strcpy(p[i].name,buf);
  32. p[i].age = 20 + i;
  33. }

2、结构体赋值
//定义结构体类型是不要直接给成员赋值
//结构体只是一个类型,还没有分配空间
//只有根据其类型定义变量时,才分配空间,有空间后才能赋值
Teacher t1 = { “lily”, “teacher”, 18, “beijing” };
//相同类型的结构体变量,可以相互赋值
//把t1每个成员的内容逐一拷贝到t2对应的成员中
t1和t2没有关系
Teacher t2 = t1;

3、结构体套指针

  1. 1)结构体嵌套一级指针类型
  2. typedef struct Teacher
  3. {
  4. char *name;
  5. int age;
  6. }Teacher;
  7. Teacher *p = NULL;
  8. p = (Teacher *)malloc(sizeof(Teacher));
  9. p->name = (char *)malloc(30);
  10. strcpy(p->name,”lilei”);
  11. p->age = 22;
  12. 2)结构体嵌套二级指针类型
  13. typedef struct Teacher
  14. {
  15. char *name;
  16. char **stu;
  17. int age;
  18. }Teacher;
  19. //1
  20. Teacher t;
  21. //t.stu[3];
  22. //char *t.stu[3];
  23. int n = 3;
  24. int i = 0;
  25. t.stu = (char **)malloc(n * sizeof(char *));
  26. for(i = 0; i < n; i++)
  27. {
  28. t.stu[i] = (char *)malloc(30);
  29. strcpy(t.stu[i],”lily”);
  30. }
  31. //2
  32. Teacher *p = NULL;
  33. //p->stu[3]
  34. p = (Teacher *)malloc(sizeof(Teacher));
  35. //char *p->stu[3]
  36. p->stu = (char **)malloc(n * sizeof(char *));
  37. //3
  38. Teacher *q = NULL;
  39. //Teacher *q[3]
  40. //q[i].stu[3]
  41. q = (Teacher *)malloc(sizeof(Teacher) * 3)
  42. for (i = 0; i < 3; i++)
  43. {
  44. //q[i].stu
  45. //(q+i)->stu
  46. q[i].stu = (char **)malloc(3 * sizeof(char *));
  47. //char *stu[3]
  48. for(j = 0; j < 3; j++)
  49. {
  50. q[i].stu[j] = (char *)malloc(30);
  51. }
  52. }

4、结构体做函数参数

  1. int getMem(Teacher **tmp, int n)
  2. {
  3. if(tmp == NULL)
  4. {
  5. return -1;
  6. }
  7. Teacher *p = (Teacher *)malloc(sizeof(Teacher) * 3);
  8. //Teacher q[3];
  9. int i = 0;
  10. char buf[30];
  11. for(i = 0; i < n; i++)
  12. {
  13. p[i].name = (char *)malloc(30);
  14. sprintf(buf,”name%d%d%d”, i, i, i);
  15. strycpy(p[i].name, buf);
  16. p[i].age = 20 + 2 * i;
  17. }
  18. *tmp = p;
  19. retrun 0;
  20. }

5、浅拷贝和深拷贝
typedef struct Teacher
{
char name;
int age;
}Teacher;
//结构体中嵌套指针,而且动态分配空间
//同类型结构体变量相互赋值
//不同结构体成员指针变量指向同一块内存
Teacher t1;
t1.name = (char
)malloc(30);
strcpy(t1.name, “lily”);
t1.age = 22;

  1. Teacher t2;
  2. t2 = t1;
  3. //深拷贝,人为增加内存,重新拷贝一下
  4. t2.name = (char *)malloc(30);
  5. strcpy(t2.name, t1.name);

6、结构体偏移量(了解)
//结构体类型定义下来,内部的成员变量的内存布局已经确定
typedef struct Teacher
{
char name[64]; //64
int age; //4
int id; //4
}Teacher;

  1. Teacher t1;
  2. Teacher *p = NULL;
  3. p = &t1;
  4. int n1 = (int)(&p->age) - (int)p; //相对于结构体首地址
  5. int n2 = (int)&((Teacher *)0)->age; //绝对0地址的偏移量

7、结构体字节对齐(以空间换时间),详情请看《结构体字节对齐规则.doc》
原则1:数据成员的对齐规则(以最大的类型字节为单位)。
原则2:结构体作为成员的对齐规则。
注意:

  1. 结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。
  2. 不是直接将结构体A的成员直接移动到结构体B中
    原则3:收尾工作
    结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
    1. struct A
    2. {
    3. int a;
    4. double b;
    5. float c;
    6. };
    7. struct
    8. {
    9. char e[2];
    10. int f;
    11. int g;
    12. short h;
    13. struct A i;
    14. }B;
    15. //对齐单位 8 个字节
    16. sizeof(B) = 40
    17. //普通成员偏移量
    18. e: 2*0 = 0
    19. f: 4*1 = 4
    20. g: 4*2 = 8
    21. h: 2*6 = 12
    22. 结构体起点坐标:
    23. 8*2 = 16
    24. //结构体成员偏移量
    25. a: 16 + 4*0 = 16
    26. b: 16 + 8*1 = 24
    27. c: 16 + 4*4 = 32

七 文件

7.1 基本概念
7.1.1 文件分类
普通文件:存放在硬盘中的文件
设备文件:屏幕、键盘等特殊文件

  1. 文本文件:ASCII文件,每个字节存放一个字符的ASCII码,打开文件看到的是文本信息
  2. 二进制文件:数据按其在内存中的存储形式原样存放,打开文件看到的是乱码

7.1.1文件缓冲区(了解)
ANSI C(标准C语言库函数)标准采用“缓冲文件系统”处理数据文件。

  1. 写文件(设备文件除外),并不会直接写到文件中,会先放在缓冲区,默认情况下,关闭文件或缓冲区满了才写到文件。
  2. 如果没有关闭文件,缓冲区也没有满,可以通过程序正常结束,或者人为刷新缓冲区fflush(fd)来把缓冲区的内容写到文件中。
  3. 缓冲区了解一下即可,增加缓冲区只是为了提高效率,减少频繁交互的次数,我们写程序基本上不用关心。

7.2 读写文件步骤
7.2.1 打开文件
//定义文件指针
FILE *fp = NULL;
fopen(“c:\demo.txt”, “w+”); //“c:\demo.txt” windows有效
//“c:/demo.txt”: 文件路径,可以是绝对路径和相对路径
//“w+”: 打开权限,读写方式打开,文件不存在则创建,写内容时,会清空原来内容再写
//“r+”:读写方式打开,文件不存在则报错
fp = fopen(“./demo.txt”, “w+”); // 45度 “c:/demo.txt” linux windows都可用
if (fp == NULL)
{
perror(“fopen”); //打印错误信息
return;
}

  1. 默认情况下,VS, Qt相对路径说明:
  2. 1)编译代码时,相对路径相对于工程目录
  3. 2)直接点击可执行程序,相对路径相对于可执行程序
  4. //”C:\\Users” windows的写法
  5. //”C:/Users” Linux,windows都支持,建议”/”
  6. c语言中有三个特殊的文件指针无需定义、打开可直接使用:
  7. stdin 标准输入 默认为当前终端(键盘)
  8. 我们使用的scanfgetchar函数默认从此终端获得数据
  9. stdout:标准输出 默认为当前终端(屏幕)
  10. 我们使用的printfputs函数默认输出信息到此终端
  11. stderr:标准出错 默认为当前终端(屏幕)
  12. 当我们程序出错或者使用: perror函数时信息打印在此终端
  13. fputc('a', stdout); //stdout -> 屏幕, 打印普通信息
  14. char ch;
  15. ch = fgetc(stdin); //std -> 键盘
  16. printf("ch = %c\n", ch);
  17. //fprintf(stderr, "%c", ch ); //stderr -> 屏幕, 错误信息
  18. fputc(ch, stderr);
  19. printf 标准输出
  20. sprintf 字符输出
  21. fprintf 文件输出

7.2.2 读写文件
1、输出,即为写,把buf中的内容写到指定的文件中
2、输入,即为读,把文件中的内容取出放在指定的buf

7.2.3 关闭文件
fclose(fp);

  1. if(fp != NULL)
  2. {
  3. fclose(fp);
  4. fp = NULL;

}

7.3 读写文件
7.3.1 库函数的学习
1)包含所需头文件
2)函数名字
3)功能
4)参数
5)返回值

7.3.2 按照字符读写文件:fgetc()、fputc()
1)写文件
char *str = “111abcdefg12345678”;
int i = 0;
for (i = 0; i < (int)strlen(str); i++)
{
//功能:往文件fp中写str[i],一个字符一个字符写
//参数:str[i]:写入文件的字符,fp:文件指针
//返回值:成功写入文件的字符,失败:-1
fputc(str[i], fp);
}

  1. 2)读文件
  2. char ch;
  3. //feof(fp)判断文件是否到结尾,已经到结尾返回值为非0,没有到结尾返回值为0
  4. while ( !feof(fp) ) //如果文件没有结尾
  5. {
  6. //返回值:成功读取的字符
  7. ch = fgetc(fp);
  8. printf("%c", ch);
  9. }

7.3.4 按照行读写文件:fputs()、fgets()
1)写文件
char *buf[] = { “11111111\n”, “aaaaaaaaaaaa\n”, “bbbbbbbbbbbb\n” }; //指针数组
int i = 0;
for (i = 0; i < 3; i++)
{
//功能:往文件fp写入一行内容buf[i]
//参数:buf[i]:字符串首地址,fp:文件指针
//返回值:成功:0,失败:非0
fputs(buf[i], fp);
}

  1. 2)读文件
  2. char buf[512] = {0};
  3. //从文件中读取一行内容(以"\n"作为标志),放在buf中
  4. //一次最大只能读sizeof(buf)-1,如果小于sizeof(buf)-1,则按实际大小读取
  5. //然后在字符串结尾自动加上字符‘\0’(转换为C风格字符串)
  6. //返回值:成功:读出的字符串,失败:NULL
  7. if (fgets(buf, sizeof(buf), fp) != NULL)
  8. {
  9. printf("buf = %s", buf);
  10. }

7.3.5 按照块读写文件:fread()、fwirte()
typedef struct Stu
{
char name[50];
int id;
}Stu;
Stu s[3];

  1. 1)写文件
  2. //写文件,按块的方式写
  3. //s:写入文件内容的内存首地址
  4. //sizeof(Stu):块数据的大小
  5. //3:块数, 写文件数据的大小 sizeof(Stu) *3
  6. //fp:文件指针
  7. //返回值,成功写入文件的块数目,不是数据总长度
  8. int ret = fwrite(s, sizeof(Stu), 3, fp);
  9. printf("ret = %d\n", ret);
  10. 2)读文件
  11. //读文件,按块的方式读
  12. //s:放文件内容的首地址
  13. //sizeof(Stu):块数据的大小
  14. //3:块数, 读文件数据的大小 sizeof(Stu) *3
  15. //fp:文件指针
  16. //返回值,成功读取文件内容的块数目,不是数据总长度
  17. int ret = fread(s, sizeof(Stu), 3, fp);
  18. printf("ret = %d\n", ret);

7.3.6 按照格式化进行读写文件:fprintf()、fscanf()
1)写文件
//格式化写文件
int a = 250;
int b = 10;
int c = 20;
//和printf()用法一样,只是printf是往屏幕(标准输出)写内容
//fprintf往指定的文件指针写内容
//返回值:成功:写入文件内容的长度,失败:负数
fprintf(fp, “Tom = %d, just like %d, it is %d”, a, b, c);

  1. 2)读文件
  2. int a, b, c;
  3. fscanf(fp, "Tom = %d, just like %d, it is %d", &a, &b, &c);
  4. printf("a = %d, b = %d, c = %d\n", a, b, c);

7.3.7 随机读写
//文件光标移动到文件结尾
//SEEK_SET:文件开头
//SEEK_CUR:文件当前位置
//SEEK_END:文件结尾
fseek(fp, 0, SEEK_END);

  1. //获取光标到文件开头文件的大小ftell
  2. long size = ftell(fp);
  3. //文件光标恢复到开始位置
  4. rewind(fp);
  5. typedef struct Stu
  6. {
  7. char name[50];
  8. int id;
  9. }Stu;
  10. Stu tmp; //读第3个结构体
  11. //假如文件中写了三个结构体
  12. //从起点位置开始,往后跳转2个结构体的位置
  13. fseek(fp, 2*sizeof(Stu), SEEK_SET);
  14. //从结尾位置开始,往前跳转一个结构体的位置
  15. //fseek(fp, -1 * (int)sizeof(Stu), SEEK_END);
  16. int ret = 0;
  17. ret = fread(&tmp,sizeof(Stu), 1, fp);
  18. if(ret == 1)
  19. {
  20. printf("[tmp]%s, %d\n", tmp.name, tmp.id);
  21. }
  22. //把文件光标移动到文件开头
  23. //fseek(fp, 0, SEEK_SET);
  24. rewind(fp);

7.4 综合案例
1)加密文件读写(使用别人写好的接口)
加密
解密
2)配置文件读写(自定义接口)

八 链表

1、数组和链表的区别
数组:一次性分配一块连续的存储区域
优点:
随机访问元素效率高
缺点:
需要分配一块连续的存储区域(很大区域,有可能分配失败)
删除和插入某个元素效率低

  1. 链表:现实生活中的灯笼
  2. 优点:
  3. 不需要一块连续的存储区域
  4. 删除和插入某个元素效率高
  5. 缺点:
  6. 随机访问元素效率低

2、相关概念
节点:链表的每个节点实际上是一个结构体变量,节点,既有 数据域 也有 指针域
typedef struct Node
{
int id; //数据域
struct Node *next; //指针域
}SLIST;

  1. 尾结点:next指针指向NULL

3、结构体套结构体
typedef struct A
{
int b;
}A;
/
1)结构体可以嵌套另外一个结构体的任何类型变量
2)结构体嵌套本结构体普通变量(不可以)
本结构体的类型大小无法确定,类型本质:固定大小内存块别名
3)结构体嵌套本结构体指针变量(可以) 链表
指针变量的空间能确定,32位, 4字节, 64位, 8字节
/
typedef struct B
{
int a;
A tmp1; //ok
A p1; //ok
//struct B tmp2; err
struct B
next; //32位, 4字节, 64位, 8字节
}B;

4、链表的分类
1)单向带头链表和不带头链表

2)双向链表带头链表和不带头链表

3)双向循环链表带头链表和不带头链表

5、链表的使用
实际上是指针的拓展应用:指向指向谁,就把谁的地址赋值给指针。
typedef struct Stu
{
int id; //数据域
char name[100];
struct Stu *next; //指针域
}Stu;

  1. 1)静态链表
  2. //初始化三个结构体变量
  3. Stu s1 = { 1, "mike", NULL };
  4. Stu s2 = { 2, "lily", NULL };
  5. Stu s3 = { 3, "lilei", NULL };
  6. s1.next = &s2; //s1的next指针指向s2
  7. s2.next = &s3;
  8. s3.next = NULL; //尾结点
  9. Stu *p = &s1;
  10. while (p != NULL)
  11. {
  12. printf("id = %d, name = %s\n", p->id, p->name);
  13. //结点往后移动一位
  14. p = p->next; //&s2
  15. }
  16. 2)动态链表
  17. //Stu *p1 = NULL;
  18. //p1 = (Stu *)malloc(sizeof(Stu));
  19. Stu *p1 = (Stu *)malloc(sizeof(Stu));
  20. Stu *p2 = (Stu *)malloc(sizeof(Stu));
  21. Stu *p3 = (Stu *)malloc(sizeof(Stu));
  22. p1->next = p2;
  23. p2->next = p3;
  24. p3->next = NULL; //尾节点
  25. Stu *tmp = p1;
  26. while(tmp != NULL)
  27. {
  28. printf("id = %d, name = %s\n", tmp->id, tmp->name);
  29. //结点往后移动一位
  30. tmp = tmp->next;
  31. }

6、链表的增、删、改、查操作

  1. 1)单向链表基本操作
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. typedef struct Node
  7. {
  8. int id; //数据域
  9. struct Node *next; //指针域
  10. }Node;
  11. //创建头结点
  12. //链表的头结点地址由函数值返回。
  13. Node *SListCreat()
  14. {
  15. Node *head = NULL;
  16. //头结点作为标志,不存储有效数据
  17. head = (Node *)malloc(sizeof(Node));
  18. if (head == NULL)
  19. {
  20. return NULL;
  21. }
  22. //给head的成员变量赋值
  23. head->id = -1;
  24. head->next = NULL;
  25. Node *pCur = head;
  26. Node *pNew = NULL;
  27. int data;
  28. while (1)
  29. {
  30. printf("请输入数据:");
  31. scanf("%d", &data);
  32. if (data == -1) //输入-1,退出
  33. {
  34. break;
  35. }
  36. //新结点动态分配空间
  37. pNew = (Node *)malloc(sizeof(Node));
  38. if (pNew == NULL)
  39. {
  40. //continue;
  41. break;
  42. }
  43. //给pNew成员变量赋值
  44. pNew->id = data;
  45. pNew->next = NULL;
  46. //链表建立关系
  47. //当前结点的next指向pNew
  48. pCur->next = pNew;
  49. //pNew下一个结点指向NULL
  50. pNew->next = NULL;
  51. //把pCur移动到pNew,pCur指向pNew
  52. pCur = pNew;
  53. }
  54. return head;
  55. }
  56. //链表的遍历
  57. int SListPrint(Node * head)
  58. {
  59. if (head == NULL)
  60. {
  61. return -1;
  62. }
  63. //取出第一个有效结点(头结点的next)
  64. Node *pCur = head->next;
  65. printf("head -> ");
  66. while (pCur != NULL)
  67. {
  68. printf("%d -> ", pCur->id);
  69. //当前结点往下移动一位,pCur指向下一个
  70. pCur = pCur->next;
  71. }
  72. printf("NULL\n");
  73. return 0;
  74. }
  75. //在值为x的结点前,插入值为y的结点;若值为x的结点不存在,则插在表尾。
  76. int SListNodeInsert(Node * head, int x, int y)
  77. {
  78. if (head == NULL)
  79. {
  80. return -1;
  81. }
  82. Node *pPre = head;
  83. Node *pCur = head->next;
  84. while (pCur != NULL)
  85. {
  86. if (pCur->id == x) //找到了匹配结点
  87. {
  88. break;
  89. }
  90. //pPre指向pCur位置
  91. pPre = pCur;
  92. pCur = pCur->next; //pCur指向下一个结点
  93. }
  94. //2种情况
  95. //1. 找匹配的结点,pCur为匹配结点,pPre为pCur上一个结点
  96. //2. 没有找到匹配结点,pCur为空结点,pPre为最后一个结点
  97. //给新结点动态分配空间
  98. Node *pNew = (Node *)malloc(sizeof(Node));
  99. if (pNew == NULL)
  100. {
  101. return -2;
  102. }
  103. //给pNew的成员变量赋值
  104. pNew->id = y;
  105. pNew->next = NULL;
  106. //插入指定位置
  107. pPre->next = pNew; //pPre下一个指向pNew
  108. pNew->next = pCur; //pNew下一个指向pCur
  109. return 0;
  110. }
  111. //删除第一个值为x的结点
  112. int SListNodeDel(Node *head, int x)
  113. {
  114. if (head == NULL)
  115. {
  116. return -1;
  117. }
  118. Node *pPre = head;
  119. Node *pCur = head->next;
  120. int flag = 0; //0没有找,1找到
  121. while (pCur != NULL)
  122. {
  123. if (pCur->id == x) //找到了匹配结点
  124. {
  125. //pPre的下一个指向pCur的下一个
  126. pPre->next = pCur->next;
  127. free(pCur);
  128. pCur = NULL;
  129. flag = 1;
  130. break;
  131. }
  132. //pPre指向pCur位置
  133. pPre = pCur;
  134. pCur = pCur->next; //pCur指向下一个结点
  135. }
  136. if (0 == flag)
  137. {
  138. printf("没有值为%d的结点\n", x);
  139. return -2;
  140. }
  141. return 0;
  142. }
  143. //清空链表,释放所有结点
  144. int SListNodeDestroy(Node *head)
  145. {
  146. if (head == NULL)
  147. {
  148. return -1;
  149. }
  150. Node * tmp = NULL;
  151. int i = 0;
  152. while (head != NULL)
  153. {
  154. //保存head的下一个结点
  155. tmp = head->next;
  156. free(head);
  157. head = NULL;
  158. //head指向tmp
  159. head = tmp;
  160. i++;
  161. }
  162. printf("i = %d \n", i);
  163. return 0;
  164. }
  165. //删除值为x的所有结点
  166. int SListNodeDelPro(Node *head, int x)
  167. {
  168. if (head == NULL)
  169. {
  170. return -1;
  171. }
  172. Node *pPre = head;
  173. Node *pCur = head->next;
  174. int flag = 0; //0没有找,1找到
  175. while (pCur != NULL)
  176. {
  177. if (pCur->id == x) //找到了匹配结点
  178. {
  179. //pPre的下一个指向pCur的下一个
  180. pPre->next = pCur->next;
  181. free(pCur);
  182. pCur = NULL;
  183. flag = 1;
  184. pCur = pPre->next;
  185. //break;
  186. continue; //跳出本次循环,重要
  187. }
  188. //pPre指向pCur位置
  189. pPre = pCur;
  190. pCur = pCur->next; //pCur指向下一个结点
  191. }
  192. if (0 == flag)
  193. {
  194. printf("没有值为%d的结点\n", x);
  195. return -2;
  196. }
  197. return 0;
  198. }
  199. //链表节点排序
  200. int SListNodeSort(Node *head)
  201. {
  202. if(head == NULL || head->next == NULL)
  203. {
  204. return -1;
  205. }
  206. Node *pPre = NULL;
  207. Node *pCur = NULL;
  208. Node tmp;
  209. // pPre->next != NULL,链表倒数第2个结点
  210. for (pPre = head->next; pPre->next != NULL; pPre = pPre->next)
  211. {
  212. for (pCur = pPre->next; pCur != NULL; pCur = pCur->next)
  213. {
  214. //注意,排序,除了数据域需要交换,next指针还需要交换
  215. if (pPre->id > pCur->id) //升序
  216. {
  217. //只交换数据域
  218. tmp.id = pCur->id;
  219. pCur->id = pPre->id;
  220. pPre->id = tmp.id;
  221. }
  222. }
  223. }
  224. return 0;
  225. }
  226. //假如原来链表是升序的,升序插入新节点
  227. //不能插入节点后再排序,是升序插入新节点x
  228. int SListNodeInsertPro(Node *head, int x)
  229. {
  230. //保证插入前是有序的
  231. int ret = SListNodeSort(head);
  232. if (ret != 0)
  233. {
  234. return ret;
  235. }
  236. if (head == NULL)
  237. {
  238. return -1;
  239. }
  240. Node *pPre = head;
  241. Node *pCur = head->next;
  242. //1 2 3 5 6, 插入4
  243. //3:pre, 5: cur
  244. while (pCur != NULL)
  245. {
  246. if (pCur->id > x) //找到了匹配结点
  247. {
  248. break;
  249. }
  250. //pPre指向pCur位置
  251. pPre = pCur;
  252. pCur = pCur->next; //pCur指向下一个结点
  253. }
  254. //给新结点动态分配空间
  255. Node *pNew = (Node *)malloc(sizeof(Node));
  256. if (pNew == NULL)
  257. {
  258. return -2;
  259. }
  260. //给pNew的成员变量赋值
  261. pNew->id = x;
  262. pNew->next = NULL;
  263. //插入指定位置
  264. pPre->next = pNew; //pPre下一个指向pNew
  265. pNew->next = pCur; //pNew下一个指向pCur
  266. return 0;
  267. return 0;
  268. }
  269. //翻转链表的节点(不是排序,是翻转)
  270. //把链表的指向反过来
  271. int SListNodeReverse(Node *head)
  272. {
  273. if (head == NULL || head->next == NULL || head->next->next == NULL)
  274. {
  275. return -1;
  276. }
  277. Node *pPre = head->next;
  278. Node *pCur = pPre->next;
  279. pPre->next = NULL; // head->next->next = NULL;
  280. Node *tmp = NULL;
  281. while (pCur != NULL)
  282. {
  283. tmp = pCur->next;
  284. pCur->next = pPre;
  285. pPre = pCur;
  286. pCur = tmp;
  287. }
  288. //head->next->next = NULL;
  289. head->next = pPre;
  290. return 0;
  291. }
  292. int main(void)
  293. {
  294. Node *head = NULL;
  295. head = SListCreat();//创建头结点
  296. SListPrint(head);
  297. SListNodeInsert(head, 5, 4);
  298. printf("在5的前面插入4后\n");
  299. SListPrint(head);
  300. SListNodeDelPro(head, 5);
  301. printf("删除所有5结点后\n");
  302. SListPrint(head);
  303. SListNodeSort(head);
  304. printf("排序后\n");
  305. SListPrint(head);
  306. SListNodeInsertPro(head, 6);
  307. printf("升序插入6后\n");
  308. SListPrint(head);
  309. SListNodeReverse(head);
  310. printf("链表翻转后\n");
  311. SListPrint(head);
  312. SListNodeDestroy(head);
  313. head = NULL;
  314. printf("\n");
  315. system("pause");
  316. return 0;
  317. }

九 函数指针

1、指针函数,它是函数,返回指针类型的函数
//指针函数
//()优先级比高,它是函数,返回值是指针类型的函数
//返回指针类型的函数
int
fun()
{
int p = (int )malloc(sizeof(int));
return p;
}

2、函数指针,它是指针,指向函数的指针,(对比数组指针的用法)
一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。

  1. 函数指针变量,它也是变量,和int a变量的本质是一样的。
  2. int fun(int a)
  3. {
  4. printf("a ========== %d\n", a);
  5. return 0;
  6. }
  7. //定义函数指针变量有3种方式:
  8. 1)先定义函数类型,根据类型定义指针变量(不常用)
  9. //有typedef是类型,没有是变量
  10. typedef int FUN(int a); //FUN是函数类型,类型模式为: int fun(int);
  11. FUN *p1 = NULL; //函数指针变量
  12. p1 = fun; //p1 指向 fun 函数
  13. fun(5); //传统调用
  14. p1(6); //函数指针变量调用方式
  15. 2)先定义函数指针类型,根据类型定义指针变量(常用)
  16. //()()优先级相同,从左往右看
  17. //第一个()代表指针,所以,它是指针
  18. //第二个括号代表函数,指向函数的指针
  19. typedef int(*PFUN)(int a); //PFUN是函数指针类型
  20. PFUN p2 = fun; //p2 指向 fun
  21. p2(7);
  22. 3)直接定义函数指针变量(常用)
  23. int(*p3)(int a) = fun;
  24. p3(8);
  25. int(*p4)(int a);
  26. p4 = fun;
  27. p4(9);

3、函数指针数组,它是数组,每个元素都是函数指针类型
void add() {}
void minus() {}
void multi() {}
void divide() {}
void myexit() {}

  1. //函数指针变量,fun1指向add()函数
  2. void(*fun1)() = add;
  3. fun1(); //调用add()函数
  4. //函数指针数组
  5. void(*fun[5])() = { add, minus, multi, divide, myexit };
  6. //指针数组
  7. char *buf[] = { "add", "min", "mul", "div", "exit" };
  8. char cmd[100];
  9. int i = 0;
  10. while (1)
  11. {
  12. printf("请输入指令:");
  13. scanf("%s", cmd);
  14. for (i = 0; i < 5; i++)
  15. {
  16. if (strcmp(cmd, buf[i]) == 0)
  17. {
  18. fun[i]();
  19. break; //跳出for()循环,最近的循环
  20. }
  21. }
  22. }

4、回调函数,函数的形参为:函数指针变量
int add(int a, int b)
{
return a + b;
}

  1. int minus(int a, int b)
  2. {
  3. return a - b;
  4. }
  5. //int(*p)(int a, int b), p 为函数指针变量
  6. //框架,固定变量
  7. //多态,多种形式,调用同一种接口,不一样表现
  8. void fun(int x, int y, int(*p)(int a, int b) )
  9. {
  10. int a = p(x, y); //回调函数
  11. printf("a = %d\n", a);
  12. }
  13. typedef int(*Q)(int a, int b); //Q 为函数指针类型
  14. void fun2(int x, int y, Q p)//p 为函数指针变量
  15. {
  16. int a = p(x, y); //回调函数
  17. printf("a = %d\n", a);
  18. }
  19. //fun()函数的调用方式
  20. fun(1, 2, add);
  21. fun2(10, 5, minus);

5、函数的递归
递归:函数可以调用函数本身(不要用main()调用main(),不是不行,是没有这么做,往往得不到你想要的结果)

  1. 1)普通函数调用(栈结构,先进后出,先调用,后结束)
  2. void funB(int b)
  3. {
  4. printf("b = %d\n", b);
  5. return;
  6. }
  7. void funA(int a)
  8. {
  9. funB(a-1);
  10. printf("a = %d\n", a);
  11. }
  12. 调用流程:
  13. funA(2) -> funB(1) -> printf(b) (离开funB(),回到funA()函数)-> printf(a)
  14. 2)函数递归调用(调用流程和上面是一样,换种模式,都是函数的调用而已)
  15. void fun(int a)
  16. {
  17. if(a == 1)
  18. {
  19. printf("a == %d\n", a);
  20. return; //中断函数很重要
  21. }
  22. fun(a-1);
  23. printf("a = %d\n", a);
  24. }
  25. fun(2);
  26. 3)递归实现累加 1+2+3+……+100
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. int fun(int n)
  6. {
  7. if (n == 1)
  8. {
  9. return n;
  10. }
  11. else
  12. {
  13. return fun(n - 1) + n;
  14. }
  15. }
  16. int main(void)
  17. {
  18. int i = 0;
  19. int sum = 0;
  20. for (i = 1; i <= 100; i++)
  21. {
  22. sum += i;
  23. }
  24. printf("sum1 = %d\n", sum);
  25. sum = fun(100);
  26. printf("\nsum2 = %d\n", sum);
  27. printf("\n");
  28. system("pause");
  29. return 0;
  30. }
  1. 4)函数递归字符串反转

十 预处理

1、C编译器提供的预处理功能主要有以下四种:
1)文件包含 #include
2)宏定义 #define
3)条件编译 #if #endif ..
4)一些特殊作用的预定义宏

2、#include< > 与 #include “”的区别
“”表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索。
< >表示系统直接按系统指定的目录检索。
注意:

  1. 1. #include <>常用于包含库函数的头文件
  2. 2. #include ""常用于包含自定义的头文件
  3. 3. 理论上#include可以包含任意格式的文件(.c .h等) ,但我们一般用于头文件的包含。

3、宏定义

  1. #define 宏名 字符串
  2. #define PI 3.14
  3. #define TEST(a,b) (a)*(b)
  4. 宏的作用域
  5. 取消宏定义
  6. #undef 宏名

4、宏定义函数

  1. #define MAX2(a,b) (a) > (b) ? (a) : (b)
  2. #define MAX3(a,b,c) (a) > (MAX2(b,c)) ? (a) : (MAX2(b,c))

5、条件编译
防止头文件被重复包含引用
//#pragma once

  1. //_FUN_H_ 自定义宏,每个头文件的宏都不一样
  2. //假如test.h->_TEST_H_
  3. #ifndef _FUN_H_
  4. #define _FUN_H_
  5. //函数的声明
  6. //宏定义
  7. //结构体
  8. #endif //!_FUN_H_

6、动态库的封装和使用
socketclient

7、日志打印
FILE
LINE

8、内存泄漏检查
memwatch

上传的附件 cloud_download C提高笔记.doc ( 740.86kb, 3次下载 )

发送私信

7
文章数
0
评论数
最近文章
eject