C语言基础
常量和宏模块
# include <stdio.h>
// 宏定义,相当于全局的静态变量,一改全改
# define URL "http://www.fishc.com"
# define NAME "鱼C工作室"
# define BOSS "小甲鱼"
# define YEAR 2021
# define MONTH 5
# define DAY 20
int main() {
printf("%s成立于%d年%d月%d日\n", NAME, YEAR, MONTH, DAY); // 字符串类型的占位符
printf("%s是%s创立的。。。\n", NAME, BOSS);
printf("%s的域名是%s\n", NAME, URL);
return 0;
}
占位符的使用
# include <stdio.h>
int main() {
int a;
char b;
float c;
double d;
a = 520;
b = 'F';
c = 3.14;
d = 3.141592653;
printf("鱼C工作室创办于2021年的%d\n", a); // %d int类型的占位符
printf("I love %cishC.com!\n", b); // %c char类型的占位符
printf("圆周率是:%.2f\n", c); // %.2f float类型占位符
printf("精确到小数点后9位的圆周率是:%11.9f\n", d); // %11.9f double 类型的占位符
return 0;
}
基本类型
- short int
- int
- long int
- long long int
浮点数类型
- float
- double
- long double
字符类型
- char
布尔类型
- _Bool
枚举类型
- enum
sizeof 运算符
法一:sizeof(对象,类型) --> 推荐
法二:sizeof 对象,类型
# include <stdio.h>
int main() {
printf("int = %d\n", sizeof(int)); // int = 4
printf("short int = %d\n", sizeof(short)); // short int = 2
printf("long int = %d\n", sizeof(long)); // long int = 4
printf("long long int = %d\n", sizeof(long long)); // long long int = 8
printf("char = %d\n", sizeof(char)); // char = 1
printf("_Bool = %d\n", sizeof(_Bool)) ; //_Bool = 1
printf("float = %d\n", sizeof(float)); // float = 4
printf("double = %d\n", sizeof(double)); // double = 8
printf("long double = %d\n", sizeof(long double)); // long double = 16
return 0;
}
signed和unsigned
signed(带符号) --> 默认
- [signed] short [int]
- 中括号可以去掉
unsigned(不带符号)
# include <stdio.h>
int main() {
short i;
unsigned short j;
i = -1;
j = -1; //这是定义了没有符号的,可是却写了-1所以结果会错
printf("%d\n", i); // -1 有符号(默认)为用 %d 占位
printf("%u\n", j); // 65535 无符号位用 %u 占位
return 0;
}
# include <stdio.h>
# include <math.h>
// 正数的源码、反码和补码都是一样的
int main() {
unsigned int result = pow(2, 32) - 1;
printf("result = %u\n", result); // 无符号的用 %u 有符号用%d
return 0;
}
字符串
char 变量名 [数量]
- eg: 声明字符串
char name[5] - eg:定义字符串
char name[5] = {‘f’, ‘i’, ‘s’, ‘h’, ‘C’};
字符串要用 \0 进行结尾
# include <stdio.h>
int main() {
// 写法一
char a[6] = {'F', 'i', 's', 'h', 'C', '\0'};
printf("%s\n", a);
printf("Hello\n");
// 写法二(推荐)
char b[] = "FishC_2";
printf("%s\n", b);
printf("Hello\n");
return 0;
}
算数运算符
- int / int 只保留整数部分,小数部分直接舍弃 eg: 5 / 3 = 1
- +5 = 5
- -5 = -5
- pow(a, b) 计算a的b次方
优先级
# include <stdio.h>
# include <math.h>
int main() {
int i, j, k;
i = 1 + 2;
j = 1 + 2 * 3;
k = i + j + -1 + pow(2, 3); // 3 + 7 + (-1) + 2^3 = 17
printf("i = %d\n", i);
printf("j = %d\n", j);
printf("k = %d\n", k);
return 0;
}
数据类型转换
# include <stdio.h>
int main() {
printf("整形输出:%d\n", 1 + 2.0); // 0
printf("整形输出:%d\n", 1 + (int)2.0); //强制数据类型转换 3
printf("整形输出:%d\n", 1 + (int)1.8); //强制数据类型转换 ,会直接舍弃小数位
printf("浮点型输出:%f\n", 1 + 2.0); // 浮点型用 %f
return 0;
}
关系运算符
- 1 为真
- 0为假
逻辑运算符
- !
- &&
- ||
# include <stdio.h>
int main() {
int a = 5;
int b = 3;
printf("%d\n", 3 > 1 && 1 < 2); // 1
printf("%d\n", 3 + 1 || 2 == 0); // 1
printf("%d\n", !(a + b)); // 0
printf("%d\n", !0 + 1 < 1 || !(3 + 4)); // 0
printf("%d\n", 'a' - 'b' && 'c'); // 1
return 0;
}
短路与、或 求值
# include <stdio.h>
int main() {
int a = 3;
int b = 3;
(a = 0) && (b = 5); // 前面为假,所以直接短路,不看 && 后面的内容了
printf("a = %d, b = %d\n", a, b); // a = 0, b = 3
(a = 1) || (b = 5); // 前面为真,所以直接短路,不看 || 后面的内容了
printf("a = %d, b = %d\n", a, b); // a = 1, b = 3
return 0;
}
小算法
# include <stdio.h>
int main() {
// 计算 1 到 100 的和
// 方法一 for
int i;
int sum = 0;
int n = 100;
for(i = 1; i <= n; i++){
sum += i;
}
printf("%d\n", sum);
// 方法二 算法 (推荐)
int i_1 = 1;
int sum_1 = 0;
int n_1 = 100;
sum_1 = (i_1 + n_1) * n_1 / 2;
printf("%d\n", sum_1);
return 0;
}
if语句
- scanf(“%d”, &i); // 接受用户输入的数据,然后赋值给 i
# include <stdio.h>
int main() {
int i;
printf("您老贵庚啊:");
scanf("%d", &i); // 接受用户输入的数据,然后赋值给 i
if(i >= 18 && i < 50) {
printf("进门左拐!\n");
} else if(i >= 50) {
printf("进门右转!\n");
} else {
printf("您请回吧。\n");
}
return 0;
}
# include <stdio.h>
int main() {
int i;
printf("请输入成绩:");
scanf("%d", &i);
if(i >= 90) {
printf("A");
} else if(i < 90 && i >= 80) {
printf("B");
} else if(i < 80 && i >= 70) {
printf("C");
} else if(i < 70 && i >= 60) {
printf("D");
} else {
printf("E");
}
return 0;
}
# include <stdio.h>
int main() {
int a,b;
printf("请输入两个数字");
scanf("%d %d", &a, &b);
if(a != b) {
if(a > b) {
printf("%d > %d\n", a, b);
} else {
printf("%d < %d\n", a, b);
}
} else {
printf("%d = %d\n", a, b);
}
return 0;
}
- getchar(); //去除回车,避免回车造成的第二次输入错误
# include <stdio.h>
int main() {
char isFree,isRain;
printf("是否有空?(Y/N)");
scanf("%c", &isFree);
getchar(); //去除回车,避免回车造成的第二次输入错误
printf("是否下雨?(Y/N)");
scanf("%c", &isRain);
if(isFree == 'Y') {
if(isRain == 'Y') {
printf("记得带伞哦。");
} else {
printf("大胆去约会吧!");
}
} else {
printf("女神没空。");
}
return 0;
}
switch语句
# include <stdio.h>
int main() {
char ch;
printf("请输入成绩:");
scanf("%c", &ch);
switch (ch) {
case 'A':
printf("你的成绩在90分以上\n");
break;
case 'B':
printf("你的成绩在80到90之间\n");
break;
case 'C':
printf("你的成绩在70到80之间\n");
break;
case 'D':
printf("你的成绩在60到70之间\n");
break;
case 'E':
printf("你的成绩小于60分");
break;
default:
printf("请输入正确的成绩等级");
break;
}
return 0;
}
while语句
# include <stdio.h>
int main() {
int i =1, sum = 0;
while(i <= 100) {
sum += i;
i++;
}
printf("结果是:%d\n", sum);
return 0;
}
# include <stdio.h>
int main() {
int count = 0;
printf("请输入一行英文字符:");
while (getchar() != '\n')
{
count++;
}
printf("你总共输入了%d个字符\n",count);
return 0;
}
for循环
# include <stdio.h>
int main() {
int num;
_Bool flag = 1;
printf("请输入一个整数");
scanf("%d", &num);
for (int i = 2; i < num / 2; i++) // c99 标准下才可以这样写,否则直接能把int i 的定义写道外面去
{
if(num % i == 0) {
flag = 0;
}
}
if (flag)
{
printf("%d是一个素数\n", num);
} else {
printf("%d不是速素数\n", num);
}
return 0;
}
# include <stdio.h>
// 打印九九乘法表
int main() {
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= i; j++)
{
printf("%d * %d = %-2d ", i, j, i * j); // %-2d 也是占位符,用来存储比较大的数字
}
printf("\n");
}
}
- break
# include <stdio.h>
int main() {
long long num;
_Bool flag = 1;
printf("请输入一个整数");
scanf("%d", &num);
for (long long i = 2; i < num / 2; i++)// c99 标准下才可以这样写,否则直接能把int i 的定义写道外面去
{
if(num % i == 0) {
flag = 0;
break; // 减少比必要的代码执行,得到代码的优化
}
}
if (flag)
{
printf("%lld是一个素数\n", num); // %lld long long 类型的占位符
} else {
printf("%lld不是一个素数\n", num);
}
return 0;
}
- continue
# include <stdio.h>
int main() {
int ch;
printf("请输入一串包含C的字符:");
while ((ch = getchar()) != '\n')
{
if (ch == 'C')
{
continue;
}
putchar(ch); // 用putchar来打印getchar() 的结果
}
putchar('\n'); // 可以用 '' 输出字符来代替printf()
return 0;
}
数组
- 定义 类型 数组名 [元素个数(常量或者是常量表达式)]
eg:- int a[6];
- char b[24];
- double c[3];
- int a[10] = {0};
# include <stdio.h>
// 宏定义
#define NUM 10
int main() {
int s[NUM];
int i, sum = 0;
for (i = 0; i < 10; i++)
{
printf("请输入地%i位同学的成绩:", i+ 1); // %i 类似 %d 一般使用%d
scanf("%d",&s[i]); // & 赋值符号
sum += s[i];
}
printf("成绩录入完毕,该次考试的平均分是%.2f\n", (double)sum / NUM);
return 0;
}
# include <stdio.h>
int main() {
int a[10] = {1, 2, 3, 4, 5}; // 可以不赋值全部的元素,其他的为零
int b[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0} ; // 有初始化的数组可以不写长度
int c[10] = {[0] = 0, [5] = 5, [8] = 8}; // c99 标准下可以给单独指定的元素赋值
int i;
for (i = 0; i < 10; i++)
{
printf("%d ", a[i]);
}
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%d ", b[i]);
}
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%d ", c[i]);
}
printf("\n");
printf("%d", sizeof(a)); // sizeof 会打印出数组所占的内存
return 0;
}
c99 标准下动态定义的数组
# include <stdio.h>
int main() {
int i, n;
printf("请输入要输入的字符串长度:");
scanf("%d", &n);
char c[n + 1];
printf("现在开始输入字符串:");
getchar(); // 把多余的回车拿掉
for (i = 0; i < n; i++)
{
scanf("%c", &c[i]);
}
c[n] = '\0'; // 字符串以 \0 结尾
printf("输入的字符串是:%s\n", c); //直接写数组的名字
return 0;
}
字符串的处理函数
# include <stdio.h>
# include <string.h>
int main() {
//1. 获取字符串的长度 strlen
char str[] = "I love FishC.com!";
printf("sizeof str = %d\n", sizeof(str)); // 18
printf("strlen str = %u\n", strlen(str)); // 17
//2. 拷贝字符串 strcpy 和 strncpy
char str1[] = "Original String";
char str2[] = "New String";
char str3[100];
strcpy(str1, str2); // strcpy(源字符串, 要被复制过去的字符串); --> 源字符串的长度 要大于 被复制过去的字符串 的长度,否则就会发生溢出
strcpy(str3, "Copy Successful");
printf("str1:%s\n", str1);
printf("str2:%s\n", str2);
printf("str3:%s\n", str3);
char str4[] = "To be or not to be";
char str5[40];
strncpy(str5, str4, 5); // 三个参数,防止字符串的溢出, 不包括最后的结束符号
str5[5] = '\0'; // 字符串以 '\0' 结尾,所以最后必须加一个 '\0'
printf("str5:%s\n", str5);
//3. 链接字符串 strcat 和 strncat
char str6[] = "I love";
char str7[] = "FishC.com!";
strcat(str6, " "); // 链接一个空格
strcat(str6, str7); // 在连接一个字符串
printf("str6: %s\n", str6);
// strncat 也是三个参数,然后在后面自动添加一个结束字符
//4. 比较字符串 strcmp 和 strncmp
// 如果两个字符串相等 则返回 0 --> 主要通过 ASCII码来比较
char str8[] = "FichC.com!";
char str9[] = "Fichc.com!";
if (!strcmp(str8, str9))
{
printf("两个字符串,完全一致");
} else
{
printf("两个字符串,存在差异");
}
return 0;
}
二维数组
- 类型 数组名 [常量表达式][常量表达式]
# include <stdio.h>
int main() {
int a[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 第一个维度可以不写,其他的都必须写
int b[][4] = {0};
int i,j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
矩阵的转置
# include <stdio.h>
int main() {
int i,j;
int a[4][5] = {
{80, 92, 85, 86, 99},
{78, 65, 89, 70, 99},
{67, 78, 76, 89, 99},
{88, 68, 98, 90, 99}
};
printf("原矩阵:\n");
for (i = 0; i < 4; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
printf("转置后的矩阵:\n");
for (i = 0; i < 5; i++) //转置的时候交换一二维数组的长度
{
for (j = 0; j < 4; j++)
{
printf("%d ",a[j][i]); // 交换i和j的位置
}
printf("\n");
}
return 0;
}
指针(重点中的重点)
指针和指针地址
- 地址 常称为指针
- 存放地址的变量 常称为指针变量
- 类型 *指针变量
取地址运算符 和 取值运算符
- 如果需要获取某个变量的地址,可以使用取地址运算符(&):
char *pa = &a;
int *pb = &f;
- 如果需要访问指针变量指向的数据,可以使用取值运算符(*): // 这里的*和定义指针时用的*是一样的
printf("%c, %d\n", *pa, *pb);
- 避免访问未初始化的指针
# include <stdio.h>
int main() {
char a = 'F';
int f = 123;
char *pa = &a; // 定义指针,--> * 是定义指针 --> & 是取到相应的地址
int *pb = &f;
printf("a = %c\n", *pa); // 这里 * 的意思不再只是定义指针,而是取值运算符
printf("b = %d\n", *pb);
// 通过指针间接访问变量
*pa = 'C';
*pb += 1;
printf("now,a = %c\n", *pa);
printf("now,b = %d\n", *pb);
printf("sizeof pa = %d\n", sizeof(pa)); // 指针存放的地址都是一样的,因为指针里存放的就是地址
printf("sizeof pb = %d\n", sizeof(pb));
printf("the addr of a is %p\n", pa); // 打印地址,%p 是字符串类型
printf("the addr of a is %p\n", pb);
return 0;
}
指针和数组
# include <stdio.h>
int main() {
int a;
int *p = &a;
printf("请输入一个整数:");
scanf("%d", &a);
printf("a = %d\n", a);
printf("请重新输入一个整数:");
scanf("%d", p); // 指针p指向的就是a的地址所以不用加 & (取地址运算符)
printf("a = %d\n", a);
return 0;
}
- 数组名其实就是一个地址信息,也是第一个数组元素的地址, 不需要用 & 即可直接访问
# include <stdio.h>
int main() {
char str[128];
printf("请输入鱼C工作室的域名:");
scanf("%d", str); //数组名不用加 &
// printf("鱼C工作室的域名是:%s\n", str);
// 打印出两个一样的地址
printf("str 的地址是:%p\n", str);
printf("str 的地址是:%p\n", &str[0]); // 第二个数是第一个数的地址加上该类型的一个元素的长度
return 0;
}
- p+1 并不是简单的讲指针的地址加1,而是指向数组的下一个元素
# include <stdio.h>
int main() {
int b[5] = {1, 2, 3, 4, 5};
int *p = b;
// p+1 并不是简单的讲指针的地址加1,而是指向数组的下一个元素
printf("*p = %d, *(p + 1) = %d, *(p + 2) = %d\n", *p, *(p+1), *(p+2));
// 用指针法来直接访问数组元素,直接用 数组名 加一
printf("*b = %d, *(b + 1) = %d, *(b + 2) = %d\n", *b, *(b+1), *(b+2));
return 0;
}
- 利用指针的形式定义字符指针型变量 字符串,然后利用数组下标的形式访问字符串
# include <stdio.h>
# include <string.h>
// 利用指针的形式定义字符指针型变量 字符串,然后利用数组下标的形式访问字符串
int main() {
// 利用指针的形式定义字符指针型变量 字符串
char *str = "I love FishC.com";
int i, length;
// 定义一个变量得到字符串长度的值,而不是直接放在for循环里面,可以避免函数的重复调用,节省资源
length = strlen(str);
// 利用数组下标的形式访问字符串
for (i = 0; i < length; i++)
{
printf("%c", str[i]);
}
printf("\n");
return 0;
}
指针数组和数组指针
- 数组名只是一个地址,而指针是一个左值 lvalue
- C 语言的术语 lvalue 指用于识别或定位一个存储位置的标识符。(注意:左值同时还必须是可改变的)
# include <stdio.h>
int main() {
char str[] = "I love FishC.com!";
// 不可直接用字符串的名字 ++ 因为自增运算符要求是左值
char *target = str; // 加入指针,指向字符串
int count = 0;
// while(*str++ != '\0') 错误写法,要求左值 lvalue
while(*target++ != '\0') { // 这里的 * 是取值运算符
count++;
}
printf("共有%d个字符!\n", count);
return 0;
}
- 指针数组 --> int *p1[5];
- 指针数组是一个数组,每个数组元素存放一个指针变量
# include <stdio.h>
int main() {
// 指针数组
char *p1[5] = {
"让编程改变世界",
"Just To Do it",
"永不止步",
"一切皆有可能",
"One more thing"
};
int i;
for (i = 0; i < 5; i++)
{
printf("%s\n", p1[i]);
}
return 0;
}
- 数组指针 --> int (*p2)[5];
- 数组指针是一个指针,它指向的是一个数组
# include <stdio.h>
int main() {
// 数组指针
// 指针在给值的时候要给地址才行
int temp[] = {1, 2, 3, 4, 5};
int (*p2)[5] = &temp; // 利用 & 取出数组的地址,直接赋值给指针数组
//如果只是给一个指针赋值,则可以如下写法,表示把数组的第一个值的地址给指针
// int *p = temp;
int i;
for (i = 0; i < 5; i++)
{
printf("%d\n", *(*p2 + i)); // 第一个 * 取值, 第二个 * 表示指针
}
return 0;
}
指针和二维数组
- 二维数组名代表的名字代表的是第一个一维数组的地址
# include <stdio.h>
int main() {
int array[4][5] = {0};
printf("int sizeof is = %d\n", sizeof(int));
printf("array: %p\n", array);
printf("array + 1 :%p\n", array + 1); // 得到的值是14(H)=10(D) 所以是一个5个int的一维数组
return 0;
}
- 取出地址的值叫做 “解引用”,
- *(array)
# include <stdio.h>
int main() {
int array[4][5] = {0};
int i,j,k = 0;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 5; j++)
{
array[i][j] = k++;
}
}
printf("*(array + 1): %p\n", *(array + 1));
printf("array[1]: %p\n", array[1]);
printf("&array[1][0]: %p\n", array[1][0]);
printf("**(array + 1): %d\n", **(array + 1));
return 0;
}
- 二维数组与数组指针
# include <stdio.h>
int main() {
int array[2][3] = {{0, 1, 2}, {3, 4, 5}};
int (*p)[3] = array; // 数组指针
// 不同索引的不同表现方式, 值相同
printf("**(p + 1): %d\n", **(p + 1));
printf("**(array + 1): %d\n", **(array + 1));
printf("array[1][0]: %d\n", array[1][0]);
//分界线
printf("======================\n");
// 不同索引的不同表现方式, 值相同
printf("*(*(p + 1) + 2): %d\n", *(*(p + 1) + 2));
printf("*(*(array + 1) + 2): %d\n", *(*(array + 1) + 2));
printf("array[1][2]: %d\n", array[1][2]);
return 0;
}
void指针 和 NULL指针
void
- void指针我们把它称为通用指针,就是可以指向任何类型的数据。也就是说,任何类型的指针都可以赋值给void指针。
# include <stdio.h>
// 其他指针转换成 void ,直接转换
// void 转换成其他类型,强制类型转换
int main() {
int num = 1024;
int *pi = #
char *ps = "FishC";
void *pv;
pv = pi; // 赋值int类型
printf("*pi: %p, *pv: %p\n", pi, pv);
//void 转换成其他类型,强制类型转换
printf("*pv: %d\n", *(int *)pv);
pv = ps; // 赋值char类型
printf("*ps: %p, *pv: %p\n", ps, pv);
//void 转换成其他类型,强制类型转换
printf("*pv: %s\n", (char *)pv);
return 0;
}
NULL
-
define NULL((void *) 0)
- 当你还不清楚要将指针初始化为什么 地址时,请将它初始化为NULL;在对指针进行解引用时,先检查指针是或否为NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。
# include <stdio.h>
int main() {
// 不对指针进行初始化,就会出现指针乱指的情况,容易使程序出错,不容易排查
int *p1;
int *p2 = NULL;
printf("*p1: %d\n", *p1);
printf("*p2: %d\n", *p2);
return 0;
}
指向指针的指针
# include <stdio.h>
int main() {
int num = 520;
int *p = #
int **pp = &p;
printf("num: %d\n", num);
printf("*p: %d\n", *p);
printf("**pp: %d\n", **pp);
printf("&p: %p, pp: %p\n", &p, pp); // &p, pp 是 p 的地址
printf("&num: %p, p: %p, *pp: %p\n", &num, p, *pp); // &num, p, *pp 是 num的地址
return 0;
}
- 指向指针的指针来指向数组指针
# include <stdio.h>
int main() {
char *cBooks[] = { // 指针数组,是数组存放指针变量
"《C程序设计语言》",
"《C专家编程》",
"《C和指针》",
"《C陷阱与缺陷》",
"《C Primer Plus》",
"《带你学C带你飞》"
};
char **byFishC;
char **jiayuLoves[5];
// byFishC 指向字符指针的指针 的变量
// cBooks[5] 就是得到第五个的值 《带你学C带你飞》
// &cBooks[5] 得到 cBooks[5] 的地址值
byFishC = &cBooks[5];
int i;
jiayuLoves[0] = &cBooks[0];
jiayuLoves[1] = &cBooks[1];
jiayuLoves[2] = &cBooks[2];
jiayuLoves[3] = &cBooks[3];
jiayuLoves[4] = &cBooks[4];
printf("FishC出版的图书有:%s\n", *byFishC);
printf("小甲鱼喜欢的书有:\n");
for (i = 0; i < 5; i++)
{
printf("%s\n", *jiayuLoves[i]);
}
return 0;
}
常量和指针
- const 可以把变量变成常量,不可以再修改,只可以读取其中的值(类似于Java中的final关键字)
- 指针可以修改为指向不同的常量
- 指针可以修改为指向不同的变量
- 可以通过解引用来读取指针指向的数据
- 不可以通过解引用修改指针指向的数据
# include <stdio.h>
int main() {
const float pi = 3.1415926; // const 把变量变成常量
printf("%f\n", pi);
return 0;
}
- 指向常量的指针
# include <stdio.h>
int main() {
int num = 520;
const int cnum = 880; // 不可修改
const int *pc = &cnum; // 不可修改
printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
printf("*pc: %d, pc: %p\n", *pc, pc);
// *pc = 1024; 解引用的形式会报错
pc = # //指针可以修改地址
printf("num: %d, &num: %p\n", num, &num);
printf("*pc: %d, pc: %p\n", *pc, pc);
num = 1024; // 通过修改num来改变指针指向的值
printf("*pc: %d, pc: %p\n", *pc, pc);
return 0;
}
- 常量指针
– 指向非常量的常量指针
- 指针不可以被修改
- 指针指向的值可以被修改
– 指向常量的常量指针
- 指针自身不可以修改
- 指针指向的值也不可以被修改
函数(一个封装的方法)
函数的类型就是函数的返回值类型
函数名相当于函数的地址
# include <stdio.h>
void print_C(); // 声明函数,不返回任何数据,默认是int类型
// 如果不做声明,那么就必须把 函数的定义 写在 函数的调用 前面
void print_C() { // 定义函数
printf(" ###### \n");
printf("## ##\n");
printf("## \n");
printf("## \n");
printf("## \n");
printf("## ##\n");
printf(" ###### \n");
}
int main() {
print_C(); // 调用函数
return 0;
}
函数的参数和返回值
- 计算"1+2+3+…+(n-1)+n的结果
# include <stdio.h>
int sum(int n);
int sum(int n) {
int result = 0;
do
{
result += n;
} while (n-- > 0);
return result;
}
int main() {
int n;
printf("请输入n的值:");
scanf("%d", &n);
printf("1+2+3+......+(n-1)+n的结果是:%d\n", sum(n));
return 0;
}
- 打印两个整数中的较大值
# include <stdio.h>
int max(int, int);
int max(int x, int y) {
if (x > y)
{
return x;
} else
{
return y;
}
}
int main() {
int a, b, c;
printf("请输入两个整数,用空格分隔:");
scanf("%d%d", &a, &b);
c = max(a, b);
printf("他们中较大的值是:%d\n", c);
return 0;
}
传值和传址
- 传值:传入普通数值
# include <stdio.h>
void swap(int x, int y);
void swap(int x, int y) {
int temp;
printf(" In swap互换前:x = %d, y = %d\n", x, y);
temp = x;
x = y;
y = temp;
printf(" In swap互换后:x = %d, y = %d\n", x, y);
}
int main() {
int x = 3, y = 5;
printf(" In main互换前:x = %d, y = %d\n", x, y);
swap(x, y);
printf(" In main互换后:x = %d, y = %d\n", x, y);
return 0;
/*
运行结果
In main互换前:x = 3, y = 5
In swap互换前:x = 3, y = 5
In swap互换后:x = 5, y = 3
In main互换后:x = 3, y = 5
*/
}
- 传址:传入地址
# include <stdio.h>
void swap(int *x, int *y); // 参数是指针,指针里面放的是地址
void swap(int *x, int *y) {
int temp;
printf(" In swap互换前:x = %d, y = %d\n", *x, *y); // 对指针进行解引用
temp = *x;// 对指针进行解引用
*x = *y;// 对指针进行解引用
*y = temp;// 对指针进行解引用
printf(" In swap互换后:x = %d, y = %d\n", *x, *y);
}
int main() {
int x = 3, y = 5;
printf(" In main互换前:x = %d, y = %d\n", x, y);
swap(&x, &y); // 参数应该传入地址
printf(" In main互换后:x = %d, y = %d\n", x, y);
return 0;
/*
运行结果
In main互换前:x = 3, y = 5
In swap互换前:x = 3, y = 5
In swap互换后:x = 5, y = 3
In main互换后:x = 5, y = 3
*/
}
传数组
# include <stdio.h>
void get_array(int a[10]);
void get_array(int a[10]) {
int i;
// 改变这个位置的元素,之后调用数组的时候都会被改动
a[5] = 520;
for (i = 0; i < 10; i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
}
int main() {
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int i;
// 不是传的整个数组,而是传递的第一个元素的地址
get_array(a);
printf("在main函数里面再打印一次...\n");
for (i = 0; i < 10; i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
# include <stdio.h>
void get_array(int b[10]);
void get_array(int b[10]){
printf("sizeof b: %d\n", sizeof(b));
}
int main() {
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("sizeof a: %d\n", sizeof(a)); // 40
get_array(a); // 8
return 0;
}
可变参数
# 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 result;
// 第一个参数表示有几个变量,后面的参数是对应的变量
result = sum(3, 1, 2, 3);
printf("result = %d\n", result);
result = sum(5, 1, 2, 3, 4, 5);
printf("result = %d\n", result);
result = sum(7, 1, 2, 3, 4, 5, 6, 7);
printf("result = %d\n", result);
return 0;
}
指针函数[ int *p() ]和函数指针[ int (*p)() ]
指针函数[ int *p() ] --> 不要返回局部变量的指针
# include <stdio.h>
// 指针函数
// 使用指针变量作为函数的返回值,就是指针函数
char *getWord(char);
char *getWord(char c) { // char类型的指针
switch (c)
{
case 'A': return "Apple";
case 'B': return "Banana";
case 'C': return "Cat";
case 'D': return "Dog";
default: return "None";
}
}
int main() {
char input;
printf("请输入一个字母:");
scanf("%c", &input);
printf("%s\n", getWord(input));
return 0;
}
函数指针[ int (*p)() ]
# include <stdio.h>
int square(int);
int square(int num) {
return num * num;
}
int main() {
int num;
int (*fp)(int); // 函数指针
// 上面的函数指针拥有整形返回值,整形的参数,所以可以之直接等于square函数
printf("请输入一个整数:");
scanf("%d", &num);
// 函数名相当一函数的地址,可以直接写
fp = square;
// & 取址运算符
// 写法二:fp = □
printf("%d * %d = %d", num, num, (*fp)(num));
// 写法二:printf("%d * %d = %d", num, num, fp(num));
return 0;
}
函数指针作为参数
# include <stdio.h>
int add(int, int);
int sub(int, int);
// 第一个参数,是一个函数指针,表示一个地址(一个函数)
// int (*fp)(int, int) 一个地址,具有这样的属性
int calc(int (*fp)(int, int), int, int);
int add(int num1, int num2) {
return num1 + num2;
}
int sub(int num1, int num2) {
return num1 - num2;
}
int calc(int (*fp)(int, int), int num1, int num2) {
return (*fp)(num1, num2); // 返回一个函数指针(调用的函数)
}
int main() {
printf("3 + 5 = %d\n", calc(add, 3, 5));
printf("3 - 5 = %d\n", calc(sub, 3, 5));
return 0;
}
函数指针作为返回值
# include <stdio.h>
int add(int, int);
int sub(int, int);
int calc(int (*)(int, int), int, int);
int (*select(char op))(int, int);
int add(int num1, int num2) {
return num1 + num2;
}
int sub(int num1, int num2) {
return num1 - num2;
}
int calc(int (*fp)(int, int), int num1, int num2) {
return (*fp)(num1, num2);
}
int (*select(char op))(int, int) {
switch(op) {
case '+': return add;
case '-': return sub;
}
}
int main() {
int num1, num2;
char op;
int (*fp)(int, int);
printf("请输入一个式子(如 1+3):");
scanf("%d%c%d", &num1, &op, &num2);
fp = select(op); // fp = select(op) 返回的是函数指针,所以用此来接收
printf("%d %c %d = %d\n", num1, op, num2, calc(fp, num1, num2));
return 0;
}
局部变量和全局变量
局部变量
# include <stdio.h>
int main() {
int i = 520;
printf("before, i = %d\n", i);
for (int i = 0; i < 10; i++) // C99 标准
{
printf("for, i = %d\n", i);
}
printf("after, i = %d\n", i);
return 0;
}
全局变量(外部变量)
- 与局部变量不同,int类型的全局变量如果没有被赋值,会默认为 0;
# include <stdio.h>
void a();
void b();
void c();
int count = 0;
void a() {
count++;
}
void b() {
count++;
}
void c() {
count++;
}
int main() {
a();
b();
c();
b();
printf("小郭今天被抱了 %d 次!", count);
return 0;
}
- 局部变量和全局变量名字相同时不会报错。
# include <stdio.h>
void func();
// a 未赋值,默认为0, b 赋值为 520
int a, b = 520;
void func() {
int b;
a = 880;
b = 120;
printf("In func, a = %d, b = %d\n", a, b);
}
int main() {
printf("In main, a = %d, b = %d\n", a, b);
func();
return 0;
}
- extern 关键字,可以i先使用全局变量,然后后面再定义
# include <stdio.h>
void func();
void func() {
// 先使用extern先使用变量,在后面再定义全局变量
extern int count;
count++;
}
// 定义全局变量
int count = 0;
int main() {
func();
printf("%d\n", count);
return 0;
}
定义和声明的区别
- 当一个变量被定义的时候,编译器为变量申请内存空间并填充一些值。
- 当一个变量被声明的时候,编译器就知道该变量被定义在其他地方。
- 声明是通知编译器该变量名及相关类型已经存在,不需要再为此申请内存空间。
- 局部变量即是声明也是定义。
- 定义只能来一次,否则就叫重复定义某个同名变量;而声明可以有很多次。
作用域和链接属性
作用域(代码块作用域,文件作用域,原型作用域,函数作用域)
代码块作用域 ({})
# include <stdio.h>
int main(void) { // 写进void 表示没有参数
int i = 100; // i1
{
int i = 110; // i2
{
int i = 120; // i3
printf("i = %d\n", i); // 120
}
// 此时i = 110
{
printf("i = %d\n", i); // 110
int i = 130; // i4
printf("i = %d\n", i); // 130
}
printf("i = %d\n", i); // 110
}
printf("i = %d\n", i); // 100
return 0;
}
文件作用域(全局变量和函数名等)
# include <stdio.h>
void func(void);
int main(void) {
extern int count;
func();
count++;
printf("In main, count = %d\n",count);
return 0;
}
int count;
void func(void) {
count++;
printf("In func, count = %d\n", count);
}
原型作用域(只适用于函数原型中的参数名)
函数作用域(主要用于对goto语句限制在函数里)
连接属性( external(外部的), internal(内部的), none(无) )
(源文件)=编译=》 =链接=》 (可执行文件)
-
external(外部的) :多个文件中声明的同名标识符表示同一个实体
-
internal(内部的) :单个文件中声明的同名标识符表示同一个实体
-
none(无) :声明的同名标识符被当作独立不同的实体
-
只有具备文件作用域(全局变量或者函数名)的标识符才能拥有external或internal的链接属性,其他作用域的标识符都是none属性。
-
默认情况下,具备文件作用域的标识符具有external属性。也就是说该标识符允许跨文件访问。对于external属性的标识符,无论在不同文件中声明多少次,表示都是同一个实体。
生存区和存储类型
静态存储区 和 自动存储区
五种不同的存储类型(auto, register, static, extern, typedef)
- auto (自动变量 ,局部变量,默认的类型)
- register(寄存器变量,没法通过取址运算符获得地址)
# include <stdio.h>
int main() {
register int i = 520;
// 报错= error: address of register variable 'i' requested
// 若想取得 i 的地址,就要去掉 register
printf("Addr of i = %p\n", &i);
return 0;
}
- static(静态局部变量)
# include <stdio.h>
void func(void);
void func(void) {
static int count = 0; // 作用域并没有改变,只是改变了变量的生存期。
printf("count = %d\n", count);
count++;
}
int main(void) {
int i;
for(i = 0; i < 10; i++) {
/*
上面的变量带了 static 则结果为 0 1 2 3 4 5 6 7 8 9
上面的变量没带 static 则结果为 0 0 0 0 0 0 0 0 0 0
*/
func();
}
return 0;
}
-
extern (告诉编译器,这个函数或者变量在前面已经被定义过了)
-
typedef (用于为数据类型定义一个别名)
递归(recursion)
递归必须有结束条件,否则程序会报错。
# include <stdio.h>
void recursion(void);
void recursion(void) {
// 做一个计数器作为结束条件
// 注意用 static 改变变量的生存周期
static int count = 10;
printf("Hi! \n");
if (--count)
{
recursion();
}
}
int main(void) {
recursion();
return 0;
}
用递归实现阶乘
- 循环实现
#include <stdio.h>
long fact(int num);
long fact(int num) {
long result = 1;
for(result; num > 1; num--) {
result *= num;
}
return result;
}
int main() {
int num;
printf("请输入一个正整数:");
scanf("%d", &num);
printf("%d的阶乘是:%d\n",num, fact(num));
return 0;
}
- 递归实现
#include <stdio.h>
long fact(int num);
long fact(int num) {
long result = 1;
// 递归开始
if (num > 0)
{
// 真正进行函数调用的地方
result = num * fact(num - 1);
} else
{
// 递归的结束条件
result = 1;
}
return result;
}
int main() {
int num;
printf("请输入一个正整数:");
scanf("%d", &num);
printf("%d的阶乘是:%d\n",num, fact(num));
return 0;
}
汉诺塔
#include <stdio.h>
void hanoi(int n, char x, char y, char z);
void hanoi(int n, char x, char y, char z){
if(n == 1){ // 结束条件,
//X只剩下一个的时候,将最下面的一个移动到Z上面
printf("%c --> %c\n", x, z);
} else {
// 汉诺塔的递归部分
hanoi(n - 1, x, z, y); // 借助z将x移动到y上
printf("%c --> %c\n", x, z);
hanoi(n - 1, y, x, z); // 借助x将y移动到z上
}
}
int main() {
int n;
printf("请输入汉诺塔的层数:");
scanf("%d", &n);
hanoi(n, 'X', 'Y', 'Z');
return 0;
}
快速排序
# include <stdio.h>
void quick_sort(int array[], int left, int right);
void quick_sort(int array[], int left, int right) {
int i = left, j = right;
int temp;
int pivot; // 基准点
// 选数组中间的元素作为基准点
pivot = array[(left + right) / 2];
while(i <= j) { // 数组左边开始的下标小于数组右边开始的下标
// 从 左到右 找到大于等于基准点的元素,找到了就先停在这个元素上
while(array[i] < pivot) {
i++;
}
// 从 右到左 找到小于等于基准点的元素,找到了就先停在这个元素上
while(array[j] > pivot){
j--;
}
// 上面两个元素都找到了
// 如果i <= j, 则互换
if(i <= j) {
temp = array[i];
array[i] = array[j];
array[j] = temp;
i++; // 向左移动一下
j--; // 向右移动一下
}
}
// 递归部分
// int i = left, j = right;
if(left < j){ // 结束条件
// 左边的数组 0 得到的j
quick_sort(array, left, j);
}
if(i < right){ // 结束条件
// 又边的数组 得到的i 右边数组的长度
quick_sort(array, i, right);
}
}
int main() {
int array[] = {73, 108, 118, 101, 70, 105, 115, 104, 67, 46, 99, 111, 109};
int i, length;
// 求数组长度
length = sizeof(array) / sizeof(array[0]);
quick_sort(array, 0, length-1);
printf("排序后的结果是:");
for(i=0;i<length;i++){
printf(" %d ", array[i]);
}
putchar('\n');
return 0;
}
动态内存管理1
- malloc
- 申请动态内存空间(存放在堆上)
- 函数原型:
void *malloc(size_t size)
- free
- 释放动态内存空间
- 函数原型:
void free(void *ptr)
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
// 在堆里面申请一个内存空间,把地址给指针
ptr = (int *)malloc(sizeof(int));
if (ptr == NULL)
{
printf("内存分配失败!\n");
exit(1);
}
printf("请输入一个整数:");
scanf("%d", ptr);
printf("你输入的整数是:%d\n", *ptr);
// 释放内存空间
free(ptr);
// printf("你输入的整数是:%d\n", *ptr); // 错误,释放以后就不存在了
return 0;
}
- calloc
- 申请并初始化一系列内存空间
- realloc
- 重新分配内存空间
动态内存管理2
# include <stdio.h>
# include <stdlib.h>
int main() {
int *ptr = NULL;
int num,i;
printf("请输入带录入整数的个数:");
scanf("%d", &num);
ptr = (int *)malloc(num * sizeof(int));
for (i = 0; i < num; i++)
{
printf("请输入第%d个整数:", i + 1);
scanf("%d", &ptr[i]);
}
printf("你录入的整数是:");
for(i = 0; i < num; i++) {
printf(" %d ", ptr[i]);
}
putchar('\n');
// 释放内存
free(ptr);
return 0;
}
- 以mem开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中:
- memset ==》 使用一个常量字节填充内存空间
- memcpy ==》 拷贝内存空间
- memmove ==》 拷贝内存空间
- memcmp ==》 比较内存空间
- memchr ==》 在内存空间中搜索一个字符
# include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 10
int main() {
int *ptr = NULL;
int i;
ptr = (int *)malloc(N * sizeof(int));
if (ptr == NULL) // 表示malloc函数调用失败
{
exit(1); // 退出程序
}
// 要初始化的内存块,指定要初始化的常量,内存块的连续尺寸
memset(ptr, 0, N * sizeof(int));
for(i = 0; i < N; i++) {
printf(" %d ", ptr[i]);
}
putchar('\n');
free(ptr);
return 0;
}
calloc
-
函数原型
- void *calloc(size_t nmemb, size_t size);
-
calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb * size),这些内存空间全部被初始化为 0。
-
calloc函数与malloc函数的一个重要区别是:
- calloc函数在申请完内存后,自动初始化该内存空间为零
- malloc函数不进行初始化操作,这里边的数据是随机的
-
两种等价的写法
// calloc() 分配内存空间并初始化
int *ptr = (int *)calloc(8, sizeof(int));
// malloc()分配内存空间并用 memset() 初始化
int *ptr = (int *)malloc(8 * sizeof(int));
memset(ptr, 0, 8 * sizeof(int));
realloc
- 函数原型:
- void *realloc(void *ptr, size_t size);
- 已经下几点是需要注意的:
- realloc函数修改ptr指向的内存空间大小为size字节
- 如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果显得内存空间大小小于旧的内存空间,可能导致数据丢失,慎用!
- 该函数将移动内存空间的数据并返回新的指针
# include <stdio.h>
# include <stdlib.h>
int main() {
int i, num;
int count = 0;
int *ptr = NULL; // 必须初始化为NULL
do{
printf("请输入一个整数(输入 -1 表示结束):");
scanf("%d", &num);
count++;
ptr = (int *)realloc(ptr, count * sizeof(int));
if (ptr == NULL)
{
exit(1);
}
ptr[count - 1] = num;
}while(num != -1);
printf("输入的整数分别是:");
for (i = 0; i < count; i++)
{
printf(" %d ", ptr[i]);
}
putchar('\n');
free(ptr);
return 0;
}
C语言的内存布局
堆:
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被删除。
栈:
平时听到的堆栈,一般指的就是这个栈。栈是函数执行的内存区域,通常和堆共享同一片区域 。
堆和栈的区别
-
申请方式:
- 堆由程序员手动申请
- 栈由系统自动分配
-
释放方式:
- 堆由程序员手动释放
- 栈由系统自动释放
-
生存周期:
- 堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问。
- 栈的生存周期由函数用开始到函数返回时结束,函数之间的局部变量不能相互访问
-
发展方向:
- 堆和其它区段一样,都是从低地址向高地址发展
- 栈则相反,是由高地址向低地址发展
宏定义的高级用法
C语言三大预处理功能
# include <stdio.h>
# define R 6371
# define PI 3.14 // 后面不能加分号
// 宏定义的嵌套,在一个宏定义使用另外一个宏定义
# define V PI * R * R * R * 4 / 3
int main() {
int r;
float s;
printf("请输入圆的半径:");
scanf("%d", &r);
//# undef PI // 结束宏定义PI的作用范围
s = PI * r * r;
printf("圆的面积是:%.2f\n", s);
printf("地球的体积大概是:%.2f\n", V);
return 0;
}
带参数的宏定义
- #define MAX(x, y)(((x) > (y)) ? (x) : (y)) // 名字和参数的括号之间不能有空格
# include <stdio.h>
#define MAX(x, y)(((x) > (y)) ? (x) : (y))
int main() {
int x,y;
printf("请输入两个整数:");
scanf("%d%d", &x, &y);
printf("%d 是较大的那个数!\n",MAX(x, y));
return 0;
}
# include <stdio.h>
# define SQUARE(x) ((x) * (x))
int main() {
int i = 1;
while(i <= 100) {
printf("%d 的平方是%d\n", i,SQUARE(i));
i++;
}
return 0;
}
内联函数和一些鲜为人知的技巧
内联函数来解决程序中函数调用的效率问题
- 加上 inline 变成内联函数
# include <stdio.h>
//加上inline关键字变成内联函数
inline int square(int x);
inline int square(int x){
return x * x;
}
int main() {
int i = 1;
while(i <= 100) {
printf("%d 的平方是%d\n", i - 1,square(i++));
}
return 0;
}
# include <stdio.h>
// # 会把后面的s(参数)变成字符串的形式
# define STR(s) # s
int main() {
printf("%s\n", STR(FISHC));
return 0;
}
# include <stdio.h>
// 将两个 字符连接起来,类似字符串的链接
# define TOGETHER(x, y) x ## y
int main() {
printf("%d\n", TOGETHER(5, 20)); // 520
return 0;
}
可变参数
- #define SHOWLIST(…) printf(#VA_ARGS)
- 其中 … 表示使用可变参数,__VA_ARGS__在预处理中被实际的参数所替换
# include <stdio.h>
// 其中 ... 表示使用可变参数,__VA_ARGS__在预处理中被实际的参数所替换
#define SHOWLIST(...) printf(#__VA_ARGS__) // 一个 # 转换成字符串
int main() {
SHOWLIST(FishC, 520, 3.14\n); // FishC, 520, 3.14
return 0;
}
# include <stdio.h>
# define PRINT(format, ...) printf(# format, ##__VA_ARGS__)
int main() {
PRINT(num = %d\n, 520); // num = 520
PRINT(Hello FishC!\n); // Hello FishC!
return 0;
}
结构体
结构体声明
struct 结构体名称
{
结构体成员1;
结构体成员2;
结构体成员3;
......
};
// 生成结构体
// 法一:局部变量
struct 结构体名称 结构体变量名
// 法二:全局变量
struct 结构体名称
{
结构体成员1;
结构体成员2;
结构体成员3;
......
}结构体变量名;
# include <stdio.h>
// 结构体
struct Book
{
char title[128];
char author[40];
float price;
unsigned int date;
char publisher[40];
}book2; // 方法二直接在结构体后面定义
// 此时 book2 是全局变量
int main(void) {
// struct 结构体名称 结构体变量名
// 方法一
struct Book book1;
printf("请输入书名:");
scanf("%s", book2.title);
printf("请输入作者:");
scanf("%s", book2.author);
printf("请输入售价:");
// 除字符串以外都要输入 &(取址运算符)
scanf("%f", &book2.price); // float类型,用 %f 来接受
printf("请输入出版日期:");
scanf("%d", &book2.date);
printf("请输入出版社:");
scanf("%s", book2.publisher);
printf("\n=====数据录入完毕=====\n");
printf("书名:%s\n", book2.title);
printf("作者:%s\n", book2.author);
printf("售价:%.2f\n", book2.price);
printf("日期:%u\n", book2.date); // 无符号整形用 %u
printf("出版社:%s\n", book2.publisher);
return 0;
}
初始化结构体变量
// 全部初始化
struct Book book = {
"《带你学C带你飞》",
"小甲鱼",
48.8,
20171111,
"清华大学出版社"
}
// 只初始化某个变量,可以按照成员顺序初始化
struct Book book {
.publisher = "清华大学出版社", //不同属性用 , 隔开
.price = 48.8,
.date = 20171111 // 最后一个变量不用加 ,
}
内存对齐
系统为了方便读取, 调整变量的内存空间,全都调整到最大的内存空间去表示。
- 例如:char int char 。两个char本来是一个内存空间,可是内存会经过调整调成四个(都是用一个剩下三个),也就是一共用 12 个空间
- 例如:char char int 。两个 char 就会直接公用一个int的空间(用两个剩下两个),也就是一共 8 个空间。
# include <stdio.h>
int main() {
struct A {
char a;
int b;
char c;
} a = {'x', 520, '0'};
struct B {
char a;
char c;
int b;
} b = {'x', '0', 520};
printf("size of a = %d\n", sizeof(a)); // 12
printf("size of b = %d\n", sizeof(b)); // 8
return 0;
}
嵌套数组
# include <stdio.h>
// 结构体的嵌套
struct Date {
int year;
int month;
int day;
};
// 结构体
struct Book
{
char title[128];
char author[40];
float price;
// 结构体的嵌套,在一个结构体里面套用另外一个结构体
struct Date date;
char publisher[40];
}book = {
"《带你学C带你飞》",
"小甲鱼",
48.8,
{2017, 11, 11},
"清华大学出版社"
};
int main(void) {
printf("书名:%s\n", book.title);
printf("作者:%s\n", book.author);
printf("售价:%.2f\n", book.price);
printf("日期:%d.%d.%d\n", book.date.year, book.date.month, book.date.day);
printf("出版社:%s\n", book.publisher);
return 0;
}
结构体数组和结构体指针
结构体数组
- 定义方法一:在声明结构体的时候进行定义
struct 结构体名称 {
结构体成员;
} 数组名[长度];
- 方法二:先声明一个结构体类型(比如上面的Book),再用此类型定义一个结构体数组:
struct 结构体名称 {
结构体成员;
};
struct 结构体名称 数组名[长度];
结构体指针
// 结构体的变量名不是指向结构体的
struct Book *pt;
pt = &book; // 使用取址运算符取到地址然后再赋值
// 指向结构体Book的指针pt
- 通过结构体指针访问结构体的成员有两种方法(.用于对象; ->用于指针):
- 法一:(*结构体指针).成员名
# include <stdio.h>
// 结构体的嵌套
struct Date {
int year;
int month;
int day;
};
// 结构体
struct Book
{
char title[128];
char author[40];
float price;
// 结构体的嵌套,在一个结构体里面套用另外一个结构体
struct Date date;
char publisher[40];
}book = {
"《带你学C带你飞》",
"小甲鱼",
48.8,
{2017, 11, 11},
"清华大学出版社"
};
int main(void) {
struct Book *pt;
pt = &book;
printf("书名:%s\n", (*pt).title);
printf("作者:%s\n", (*pt).author);
printf("售价:%.2f\n", (*pt).price);
printf("日期:%d-%d-%d\n", (*pt).date.year, (*pt).date.month, (*pt).date.day);
printf("出版社:%s\n", (*pt).publisher);
return 0;
}
- 法二:结构体指针->成员名
include <stdio.h>
// 结构体的嵌套
struct Date {
int year;
int month;
int day;
};
// 结构体
struct Book
{
char title[128];
char author[40];
float price;
// 结构体的嵌套,在一个结构体里面套用另外一个结构体
struct Date date;
char publisher[40];
}book = {
“《带你学C带你飞》”,
“小甲鱼”,
48.8,
{2017, 11, 11},
“清华大学出版社”
};
int main(void) {
struct Book *pt;
pt = &book;
printf("书名:%s\n", pt->title);
printf("作者:%s\n", pt->author);
printf("售价:%.2f\n", pt->price);
printf("日期:%d-%d-%d\n", pt->date.year, pt->date.month, pt->date.day);
printf("出版社:%s\n", pt->publisher);
return 0;
}
传递结构体变量和结构体指针
传递结构体变量(结构体太大,效率低)
# include <stdio.h>
int main() {
struct Test
{
int x;
int y;
}t1, t2; // 定义两个变量
t1.x = 3;
t1.y = 4;
t2 = t1; // 两个结构体之间是可以直接赋值的
printf("t2.x = %d, t2.y = %d\n", t2.x, t2.y);
return 0;
}
# include <stdio.h>
struct Date {
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;
char publisher[40];
};
struct Book getInput(struct Book book);
void printBook(struct Book book);
struct Book getInput(struct Book book) {
printf("请输入书名:");
scanf("%s", book.title);
printf("请输入作者:");
scanf("%s", book.author);
printf("请输入售价:");
// 除字符串以外都要输入 &(取址运算符)
scanf("%f", &book.price); // float类型,用 %f 来接受
printf("请输入出版日期:");
scanf("%d-%d-%d", &book.date.year, &book.date.month, &book.date.day);
printf("请输入出版社:");
scanf("%s", book.publisher);
return book;
}
void printBook(struct Book book) {
printf("书名:%s\n", book.title);
printf("作者:%s\n", book.author);
printf("售价:%.2f\n", book.price);
printf("日期:%d-%d-%d\n", book.date.year, book.date.month, book.date.day);
printf("出版社:%s\n", book.publisher);
}
int main(void) {
struct Book b1,b2;
printf("请录入第一本数的信息...\n");
b1 = getInput(b1);
putchar('\n');
printf("请录入第二本数的信息...\n");
b2 = getInput(b2);
printf("\n\n录入完毕,现在开始打印验证...\n\n");
printf("打印第一本书的信息...\n");
printBook(b1);
putchar('\n');
printf("打印第二本书的信息...\n");
printBook(b2);
return 0;
}
传递结构体指针(效率高)
# include <stdio.h>
struct Date {
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;
char publisher[40];
};
void getInput(struct Book *book);
void printBook(struct Book *book);
void getInput(struct Book *book) { // 指针不需要有返回值
printf("请输入书名:");
scanf("%s", book->title);
printf("请输入作者:");
scanf("%s", book->author);
printf("请输入售价:");
scanf("%f", &book->price); // float类型,用 %f 来接受
printf("请输入出版日期:");
scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
printf("请输入出版社:");
scanf("%s", book->publisher);
}
void printBook(struct Book *book) {
printf("书名:%s\n", book->title);
printf("作者:%s\n", book->author);
printf("售价:%.2f\n", book->price);
printf("日期:%d-%d-%d\n", book->date.year, book->date.month, book->date.day);
printf("出版社:%s\n", book->publisher);
}
int main(void) {
struct Book b1,b2;
printf("请录入第一本数的信息...\n");
getInput(&b1); // 指针类型传入地址值
putchar('\n');
printf("请录入第二本数的信息...\n");
getInput(&b2);
printf("\n\n录入完毕,现在开始打印验证...\n\n");
printf("打印第一本书的信息...\n");
printBook(&b1);
putchar('\n');
printf("打印第二本书的信息...\n");
printBook(&b2);
return 0;
}
使用malloc函数为结构体分配存储空间
# include <stdio.h>
# include <stdlib.h>
struct Date {
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[40];
float price;
struct Date date;
char publisher[40];
};
void getInput(struct Book *book);
void printBook(struct Book *book);
void getInput(struct Book *book) { // 指针不需要有返回值
printf("请输入书名:");
scanf("%s", book->title);
printf("请输入作者:");
scanf("%s", book->author);
printf("请输入售价:");
scanf("%f", &book->price); // float类型,用 %f 来接受
printf("请输入出版日期:");
scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
printf("请输入出版社:");
scanf("%s", book->publisher);
}
void printBook(struct Book *book) {
printf("书名:%s\n", book->title);
printf("作者:%s\n", book->author);
printf("售价:%.2f\n", book->price);
printf("日期:%d-%d-%d\n", book->date.year, book->date.month, book->date.day);
printf("出版社:%s\n", book->publisher);
}
int main(void) {
struct Book *b1, *b2; // 定义结构体指针
// malloc 申请指针空间
// 通过 malloc 申请一个 结构体Book 类型的指针,大小为 struct Book 结构体的大小,赋值给 b1 变量
b1 = (struct Book *)malloc(sizeof(struct Book));
b2 = (struct Book *)malloc(sizeof(struct Book));
if (b1 == NULL || b2 == NULL) { // 判断指针是否申请成功,若不成功直接退出
printf("内存分配失败!\n");
// 调用 exit(1) 需要引用 # include <stdlib.h> 头文件
exit(1);
}
printf("请录入第一本数的信息...\n");
getInput(b1); // 指针类型传入地址值
putchar('\n');
printf("请录入第二本数的信息...\n");
getInput(b2);
printf("\n\n录入完毕,现在开始打印验证...\n\n");
printf("打印第一本书的信息...\n");
printBook(b1);
putchar('\n');
printf("打印第二本书的信息...\n");
printBook(b2);
// 释放空间,与malloc相互对应
free(b1);
free(b2);
return 0;
}
单链表
# include <stdio.h>
int main() {
struct Test {
int x;
int y;
struct Test *test;
};
return 0;
}
- 链表包括信息域和指针域
图书馆信息存入(头插法)
# include <stdio.h>
# include <stdlib.h>
struct Book {
// 信息域
char title[128];
char author[40];
// 指针域
struct Book *next;
};
// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
void releaseLibrary(struct Book **library);
void getInput(struct Book *book) {
printf("请输入书名:");
scanf("%s", book->title);
printf("请输入作者:");
scanf("%s",book->author);
}
// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
struct Book *book, *temp;
book = (struct Book *)malloc(sizeof(struct Book));
if(book == NULL) {
printf("内存分配失败了! \n");
exit(1);
}
getInput(book); // 传入指针book
if(*library != NULL) {
/* 头插法 */
temp = *library;
*library = book;
book->next = temp;
} else { // 如果为 NULL 则代表是空链表
*library = book;
book->next = NULL;
}
}
void printLibrary(struct Book *library) {
struct Book *book;
int count = 1;
book = library;
while(book != NULL){
printf("Book: %d \n", count);
printf("书名:%s \n", book->title);
printf("作者: %s \n", book->author);
book = book->next;
count++;
}
}
void releaseLibrary(struct Book **library) {
struct Book *temp;
while(*library != NULL) {
temp = *library;
*library = (*library)->next;
free(temp);
}
}
int main() {
// 作为头指针,初始值为NULL
struct Book *library = NULL;
int ch;
// 录入细信息
while(1) {
printf("请问是否需要录入书籍信息(Y/N):");
do{
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
// 传入一个地址,不能直接传入library
addBook(&library);
} else {
break;
}
}
printf("请问是否需要打印图书信息(Y/N):");
do {
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
printLibrary(library);
}
// 不需要以后直接释放掉
releaseLibrary(&library);
return 0;
}
图书信息存入(尾插法)
# include <stdio.h>
# include <stdlib.h>
struct Book {
// 信息域
char title[128];
char author[40];
// 指针域
struct Book *next;
};
// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
void releaseLibrary(struct Book **library);
void getInput(struct Book *book) {
printf("请输入书名:");
scanf("%s", book->title);
printf("请输入作者:");
scanf("%s",book->author);
}
// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
struct Book *book, *temp;
book = (struct Book *)malloc(sizeof(struct Book));
if(book == NULL) {
printf("内存分配失败了! \n");
exit(1);
}
getInput(book); // 传入指针book
if(*library != NULL) {
/* 尾插法 */
temp = *library;
//定义单链表的尾部位置
while(temp->next != NULL) { // 遍历找到单链表的尾部位置
temp = temp->next;
}
// 插入数据
temp->next = book;
book->next = NULL;
} else { // 如果为 NULL 则代表是空链表
*library = book;
book->next = NULL;
}
}
void printLibrary(struct Book *library) {
struct Book *book;
int count = 1;
book = library;
while(book != NULL){
printf("Book: %d \n", count);
printf("书名:%s \n", book->title);
printf("作者: %s \n", book->author);
book = book->next;
count++;
}
}
void releaseLibrary(struct Book **library) {
struct Book *temp;
while(*library != NULL) {
temp = *library;
*library = (*library)->next;
free(temp);
}
}
int main() {
// 作为头指针,初始值为NULL
struct Book *library = NULL;
int ch;
// 录入细信息
while(1) {
printf("请问是否需要录入书籍信息(Y/N):");
do{
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
// 传入一个地址,不能直接传入library
addBook(&library);
} else {
break;
}
}
printf("请问是否需要打印图书信息(Y/N):");
do {
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
printLibrary(library);
}
// 不需要以后直接释放掉
releaseLibrary(&library);
return 0;
}
图书信息存入(尾插法(优化))
- 添加tail始终指向单链表的尾部节点
# include <stdio.h>
# include <stdlib.h>
struct Book {
// 信息域
char title[128];
char author[40];
// 指针域
struct Book *next;
};
// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
void releaseLibrary(struct Book **library);
void getInput(struct Book *book) {
printf("请输入书名:");
scanf("%s", book->title);
printf("请输入作者:");
scanf("%s",book->author);
}
// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
struct Book *book;
/* 静态变量,保存单链表尾部的位置 */
static struct Book *tail;
book = (struct Book *)malloc(sizeof(struct Book));
if(book == NULL) {
printf("内存分配失败了! \n");
exit(1);
}
getInput(book); // 传入指针book
if(*library != NULL) {
/* 尾插法(优化) */
tail->next = book;
book->next = NULL;
} else { // 如果为 NULL 则代表是空链表
*library = book;
book->next = NULL;
}
tail = book;
}
void printLibrary(struct Book *library) {
struct Book *book;
int count = 1;
book = library;
while(book != NULL){
printf("Book: %d \n", count);
printf("书名:%s \n", book->title);
printf("作者: %s \n", book->author);
book = book->next;
count++;
}
}
void releaseLibrary(struct Book **library) {
struct Book *temp;
while(*library != NULL) {
temp = *library;
*library = (*library)->next;
free(temp);
}
}
int main() {
// 作为头指针,初始值为NULL
struct Book *library = NULL;
int ch;
// 录入细信息
while(1) {
printf("请问是否需要录入书籍信息(Y/N):");
do{
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
// 传入一个地址,不能直接传入library
addBook(&library);
} else {
break;
}
}
printf("请问是否需要打印图书信息(Y/N):");
do {
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
printLibrary(library);
}
// 不需要以后直接释放掉
releaseLibrary(&library);
return 0;
}
按照书名和作者名搜索图书
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
struct Book {
// 信息域
char title[128];
char author[40];
// 指针域
struct Book *next;
};
// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
struct Book *searchBook(struct Book *library, char *target);
void printBook(struct Book *book);
void releaseLibrary(struct Book **library);
void getInput(struct Book *book) {
printf("请输入书名:");
scanf("%s", book->title);
printf("请输入作者:");
scanf("%s",book->author);
}
// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
struct Book *book;
/* 静态变量,保存单链表尾部的位置 */
static struct Book *tail;
book = (struct Book *)malloc(sizeof(struct Book));
if(book == NULL) {
printf("内存分配失败了! \n");
exit(1);
}
getInput(book); // 传入指针book
if(*library != NULL) {
/* 尾插法(优化) */
tail->next = book;
book->next = NULL;
} else { // 如果为 NULL 则代表是空链表
*library = book;
book->next = NULL;
}
tail = book;
}
void printLibrary(struct Book *library) {
struct Book *book;
int count = 1;
book = library;
while(book != NULL){
printf("Book: %d \n", count);
printf("书名:%s \n", book->title);
printf("作者: %s \n", book->author);
book = book->next;
count++;
}
}
// 通过书名或者作者,搜索图书部分的函数
struct Book *searchBook(struct Book *library, char *target) {
struct Book *book;
book = library;
while(book != NULL) {
// strcmp 需要
if(!strcmp(book->title, target) || !strcmp(book->author, target)) {
break;
}
book = book->next;
}
return book;
}
// 找到以后打印图书
void printBook(struct Book *book) {
printf("书名:%s", book->title);
printf("作者:%s", book->author);
}
void releaseLibrary(struct Book **library) {
struct Book *temp;
while(*library != NULL) {
temp = *library;
*library = (*library)->next;
free(temp);
}
}
int main() {
// 作为头指针,初始值为NULL
struct Book *library = NULL;
struct Book *book;
char input[128];
int ch;
// 录入细信息
while(1) {
printf("请问是否需要录入书籍信息(Y/N):");
do{
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
// 传入一个地址,不能直接传入library
addBook(&library);
} else {
break;
}
}
printf("请问是否需要打印图书信息(Y/N):");
do {
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if(ch == 'Y') {
printLibrary(library);
}
printf("\n请输入书名或作者:\n");
scanf("%s", input);
book = searchBook(library,input);
if(book == NULL) {
printf("很抱歉,没能找到!\n");
} else {
do {
printf("已找到符合条件的图书...\n");
printBook(book);
} while((book = searchBook(book->next, input)) != NULL);
}
// 不需要以后直接释放掉
releaseLibrary(&library);
return 0;
}
单链表的插入
# include <stdio.h>
# include <stdlib.h>
struct Node {
int value;
struct Node *next;
};
void insertNode(struct Node **head, int value);
void printNode(struct Node *head);
// 传入值指向Node结构体指针的指针,所以传 **head
void insertNode(struct Node **head, int value) {
// previous --> new --> current
// 记录要插入节点的上一个节点
struct Node *previous;
// 表示当前的指针,指向比value大的节点的指针
// 记录要插入节点的下一个节点
struct Node *current;
// 要插入的新节点
struct Node *new;
current = *head;
previous = NULL;
// current != NULL 是空链表的情况
// current->value < value 找到要拆入的节点的位置就停止循环
while(current != NULL && current->value < value) {
previous = current;
current = current->next;
}
new = (struct Node *)malloc(sizeof(struct Node));
if (new == NULL)
{
printf("内存分配失败!");
exit(1);
}
new->value = value;
new->next = current;
if(previous == NULL) { // 传入的是空链表
//直接吧new节点放在第一个
*head = new;
} else {
previous->next = new;
}
}
void printNode(struct Node *head) {
struct Node *current;
current = head;
while(current != NULL) {
printf(" %d ", current->value);
current = current->next;
}
putchar('\n');
}
int main() {
struct Node *head = NULL; //定义一个头指针
int input;
while(1) {
printf("请输入一个整数(输入-1表示结束):");
scanf("%d", &input);
if(input == -1) {
printf("程序结束。");
break;
}
insertNode(&head, input);
printNode(head);
}
return 0;
}
单链表的删除
# include <stdio.h>
# include <stdlib.h>
struct Node {
int value;
struct Node *next;
};
void insertNode(struct Node **head, int value);
void printNode(struct Node *head);
// 传入值指向Node结构体指针的指针,所以传 **head
void insertNode(struct Node **head, int value) {
// previous --> new --> current
// 记录要插入节点的上一个节点
struct Node *previous;
// 表示当前的指针,指向比value大的节点的指针
// 记录要插入节点的下一个节点
struct Node *current;
// 要插入的新节点
struct Node *new;
current = *head;
previous = NULL;
// current != NULL 是空链表的情况
// current->value < value 找到要拆入的节点的位置就停止循环
while(current != NULL && current->value < value) {
previous = current;
current = current->next;
}
new = (struct Node *)malloc(sizeof(struct Node));
if (new == NULL)
{
printf("内存分配失败!");
exit(1);
}
new->value = value;
new->next = current;
if(previous == NULL) { // 传入的是空链表
//直接吧new节点放在第一个
*head = new;
} else {
previous->next = new;
}
}
void printNode(struct Node *head) {
struct Node *current;
current = head;
while(current != NULL) {
printf(" %d ", current->value);
current = current->next;
}
putchar('\n');
}
void deleteNode(struct Node **head, int value) {
struct Node *previous;
struct Node *current;
current = *head;
previous = NULL;
while(current != NULL && current->value != value) {
previous = current;
current = current->next;
}
if(current == NULL) {
printf("找不到匹配的节点!\n");
return ;
} else {
if(previous == NULL) {
*head = current->next;
} else {
previous->next = current->next;
}
free(current);
}
}
int main() {
struct Node *head = NULL; //定义一个头指针
int input;
printf("开始测试插入整数:...\n");
while(1) {
printf("请输入一个整数(输入-1表示结束):");
scanf("%d", &input);
if(input == -1) {
printf("程序结束。");
break;
}
insertNode(&head, input);
printNode(head);
}
printf("开始测试删除整数:...\n");
while(1) {
printf("请输入一个整数(输入-1表示结束):");
scanf("%d", &input);
if(input == -1) {
break;
}
deleteNode(&head, input);
printNode(head);
}
return 0;
}
内存池
- 内存池:就是让程序额外维护一个缓存区域
- 使用单链表为维护一个简单的内存池
- 只需要将没有用的内存空间地址一次用一个单链表记录下来;再次需要的时候,从这个单链表中获取即可。
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# define MAX 1024
struct Person {
char name[40];
char phone[20];
struct Person *next;
};
// 定义一个把内存池(单链表);
struct Person *pool = NULL;
int count;
// 得到用户输入的数据
void getInput(struct Person *person);
// 打印出通讯录中的人员和信息
void printPerson(struct Person *person);
// 添加用户
void addPerson(struct Person **contacts);
// 修改用户的电话号码
void changePerson(struct Person *contacts);
// 删除用户的信息
void delPerson(struct Person **contacts);
// 查找用户,返回一个指向Person结构体的指针
struct Person *findPerson(struct Person *contacts);
// 显示整个通讯录的信息
void displayContacts(struct Person *contacts);
// 释放通讯录
void releaseContacts(struct Person **contacts);
// 释放内存池
void releasePool();
void getInput(struct Person *person) {
printf("请输入姓名:");
scanf("%s", person->name);
printf("请输入电话:");
scanf("%s", person->phone);
}
void printPerson(struct Person *person) {
printf("联系人:%s\n", person->name);
printf("电话:%s\n", person->phone);
}
void addPerson(struct Person **contacts) {
struct Person *person;
struct Person *temp;
// 如果内存池非空,则之家在里面获取空间
if(pool != NULL) {
person = pool;
pool = pool->next;
count--;
} else {
// 如果内存池为空,则调用malloc函数申请新的内存空间
person = (struct Person *)malloc(sizeof(struct Person));
if(person == NULL) {
printf("内存分配失败!\n");
exit(1);
}
}
getInput(person);
// 将person用头插法添加到通讯录中
if(*contacts != NULL) {
temp = *contacts;
*contacts = person;
person->next = temp;
} else {
*contacts = person;
person->next = NULL;
}
}
void changePerson(struct Person *contacts) {
struct Person *person;
person = findPerson(contacts);
if(person == NULL) {
printf("找不到该联系人!\n");
} else {
printf("请输入新的联系电话:");
scanf("%s", person->phone);
}
}
void delPerson(struct Person **contacts) {
struct Person *temp;
struct Person *person;
struct Person *current;
struct Person *previous;
//先找到要删除的节点指针
person = findPerson(*contacts);
if(person == NULL) {
printf("找不到该联系人!\n");
} else {
current = *contacts;
previous = NULL;
// current定位到待删除的节点
while(current != NULL && current != person) {
previous = current;
current = current->next;
}
if(previous == NULL) {
// 待删除的节点是第一个节点
*contacts = current->next;
} else {
// 待删除的节点不是第一个节点
previous->next = current->next;
}
// 先判断内存池是不是有空位
if(count < MAX) {
// 头插法
if(pool != NULL) { // pool不是第一个节点
temp = pool;
pool = person;
person->next = temp;
} else {
pool = person;
person->next = NULL;
}
count++;
} else {
free(person);
}
printf("该联系人已删除。");
}
}
struct Person *findPerson(struct Person *contacts) {
struct Person *current;
char input[40];
printf("请输入联系人:");
scanf("%s", input);
current = contacts;
while (current != NULL && strcmp(current->name, input)) {
current = current->next;
}
return current;
}
void displayContacts(struct Person *contacts) {
struct Person *current;
int flg = 1;
current = contacts;
// 传近来的通讯录里面有人
while(current != NULL) {
flg = 0;
printPerson(current);
current = current->next;
}
if(current == NULL && flg) {
printf("通讯录为空,我未找到任何联系人!");
}
}
// 释放通讯录
void releaseContacts(struct Person **contacts) {
struct Person *temp;
while(*contacts != NULL) {
temp = *contacts;
*contacts = (*contacts)->next;
free(temp);
}
printf("程序已退出。");
}
//释放内存池
void releasePool() {
struct Person *temp;
while(pool != NULL) {
temp = pool;
pool = pool->next;
free(temp);
}
}
int main() {
int code; // 程序指令代码
struct Person *contacts = NULL;
struct Person *person;
printf("|- 欢迎使用通讯录管理程序 -|\n");
printf("|--- 1:插入新的联系人 ---|\n");
printf("|--- 2:查找已有联系人 ---|\n");
printf("|--- 3:更改已有联系人 ---|\n");
printf("|--- 4:删除已有联系人 ---|\n");
printf("|--- 5:显示已有联系人 ---|\n");
printf("|--- 6:退出通讯录程序 ---|\n");
printf("|- Powered by Fishc.com -|\n");
while(1) {
printf("\n请输入指令代码:");
scanf("%d", &code);
switch(code) {
case 1: addPerson(&contacts);
break;
case 2: person = findPerson(contacts); // 返回一个结构体,找不到联系人就返回空
if(person == NULL) {
printf("找不到该联系人! \n");
} else {
printPerson(person);
}
break;
case 3: changePerson(contacts);
break;
case 4: delPerson(&contacts); // 删除联系人
break;
case 5: displayContacts(contacts);
break;
case 6: goto END; // goto 语句实现语句的跳转,尽量少用,或者不用
}
}
END:
releaseContacts(&contacts);
releasePool();
return 0;
}
基础typedef(给变量起别名)
- 是C语言最重要的关键字之一
- 是对一个类型的封装
# include <stdio.h>
typedef int integer;
int main() {
integer a;
int b;
a = 520;
b = a;
printf("a = %d\n", a);
printf("b = %d\n", a);
printf("size of a = %d\n", sizeof(a));
return 0;
}
- 可以一次性起好几个别名
- 可以是 变量名,也可以是一个指针
# include <stdio.h>
// 可以一次给一个变量起好几个别名,用“逗号”隔开
// 可以是 变量名,也可以是一个指针
typedef int INTEGER, *PIRINT;
int main() {
INTEGER a = 520;
PIRINT b,c;
b = &a;
c = b;
printf("addr of a = %p\n", c);
return 0;
}
typedef 与结构体紧密相关(很重要与数据结构紧密相关)
# include <stdio.h>
# include <stdlib.h>
// 使用type给结构体去一个 别名 和 指针
// 别名 代替 struct Date 的反复书写
// 指针 用来代替用到机构体指针的地方
typedef struct Date {
int year;
int month;
int day;
}DATE, *PDATE;
int main() {
struct Date *date;
// 未利用 typedef起别名和指针 之前
// date = (struct Date *)malloc(sizeof(struct Date));
// 利用 typedef起别名和指针 之后
date = (PDATE)malloc(sizeof(DATE));
if(date == NULL) {
printf("内存分配失败!\n");
exit(1);
}
date->year = 2022;
date->month = 3;
date->day = 12;
printf("%d-%d-%d\n", date->year, date->month, date->day);
return 0;
}
进阶typedef
-
在编程中使用typedef目的一般有两个
- 一个是给变量起一个容易记住且意义明确的别名;
- 另一个是简化一些复杂的类型声明。
-
数组指针
# include <stdio.h>
// 数组指针起别名
typedef int (*PTR_TO_ARRAY)[3];
// int (*ptr)[3]; ==》 typedef int (*PTR_TO_ARRAY)[3];
int main() {
int array[3] = {1, 2, 3};
PTR_TO_ARRAY ptr_to_array = &array;
int i;
for(i = 0; i < 3; i++) {
// 先进行解引用然后在逐个进行便利
printf("%d\n", (*ptr_to_array)[i]);
}
return 0;
}
- 函数指针
# include <stdio.h>
typedef int (*PTR_TO_FUN)(void);
int fun(void) {
return 520;
}
// 函数指针 int (*fun)(void) ==》 typedef int (*PTR_TO_FUN)(void)
int main() {
PTR_TO_FUN ptr_to_fun = &fun;
printf("%d\n", (*ptr_to_fun)());
return 0;
}
- 指针函数
# include <stdio.h>
/*
指针函数 int *(*array[3])(int);
==》
typedef int *(*PTR_TO_FUN)(int);
PTR_TO_FUN array[3];
*/
typedef int *(*PTR_TO_FUN)(int); // 这样声明可以灵活的定义数组的长度
// 例如:PTR_TO_FUN array[3];
int *funA(int num) {
printf("%d\t", num);
return #
}
int *funB(int num) {
printf("%d\t", num);
return #
}
int *funC(int num) {
printf("%d\t", num);
return #
}
int main() {
// 不加 & 也可以,因为函数名就是这个函数的地址
PTR_TO_FUN array[3] = {&funA, &funB, &funC};
int i;
for(i = 0; i < 3; i++) {
printf("addr of num %p\n", (*array[i])(i));
}
return 0;
}
# include <stdio.h>
/*
void (*funA(int, void(*funB)(int)))(int);
==》
typedef void (*PTR_TO_FUN)(void);
PTR_TO_FUN funA(int, PTR_TO_FUN);
*/
typedef int (*PTR_TO_FUN)(int, int);
int add(int, int);
int sub(int, int);
/*
// typeof 简化前
int calc(int (*)(int, int), int, int);
int (*select(char op))(int, int);
*/
// typeof 简化后
int calc(int (*)(int, int), int, int);
int (*select(char op))(int, int);
int add(int num1, int num2) {
return num1 + num2;
}
int sub(int num1, int num2) {
return num1 - num2;
}
int calc(int (*fp)(int, int), int num1, int num2) {
return (*fp)(num1, num2);
}
int (*select(char op))(int, int) {
switch(op) {
case '+': return add;
case '-': return sub;
}
}
int main() {
int num1, num2;
char op;
int (*fp)(int, int);
printf("请输入一个式子(如 1+3):");
scanf("%d%c%d", &num1, &op, &num2);
fp = select(op); // fp = select(op) 返回的是函数指针,所以用此来接收
printf("%d %c %d = %d\n", num1, op, num2, calc(fp, num1, num2));
return 0;
}
共用体
- 共用体的成员拥有同一个内存 地址!
union 共用体名称 {
共用体成员1;
共用体成员2;
共用体成员3;
......
};
# include <stdio.h>
# include <string.h>
// 共用体,使用的是同一个地址。
// 在使用的时候只能使用其中一个
union Test {
int i;
double pi;
char str[6];
};
int main() {
union Test test;
test.i = 520;
test.pi = 3.14;
// 拷贝字符串
strcpy(test.str, "FishC");
// 地址使用%p
printf("addr of test.i: %p\n", &test.i);
printf("addr of test.pi: %p\n", &test.pi);
printf("addr of test.str: %p\n", &test.str);
return 0;
}
枚举类型
- 一 一列举
// 声明枚举类型
enum 枚举类型名称 {
枚举值名称,
枚举值名称,
...
};
// 声明枚举变量
enum 枚举类型名称 枚举变量1, 枚举变量2;
# include <stdio.h>
# include <time.h>
int main() {
// 枚举常量,默认 int 类型,从0开始
enum Week {sun, mon, tue, wed, thu, fri, sat};
// 枚举变量
enum Week today;
struct tm *p;
time_t t;
time(&t);
p = localtime(&t);
today = p->tm_wday;
switch(today) {
case mon:
case tue:
case wed:
case thu:
case fri:
printf("干活!\n");
break;
case sat:
case sun:
printf("休息!\n");
break;
default:
printf("");
}
return 0;
}
# include <stdio.h>
int main() {
// 枚举常量,默认 int 类型,为常量,指定后不能改变值
enum Color {red = 10, green, blue};
enum Color rgb;
for(rgb = red; rgb <= blue; rgb++) {
printf("rgb is %d\n", rgb);
}
return 0;
}
# include <stdio.h>
int main() {
// 如果从中间赋值,前面的部分还是会从 0 开始
enum Color {red, green, yellow = 10, blue};
enum Color rgb;
printf("red = %d\n", red); // 0
printf("green = %d\n", green); // 1
printf("yellow = %d\n", yellow); // 10
printf("blue = %d\n", blue); // 11
return 0;
}
位域(位段,位字段)
- 一般只有int类型使用位域
- 可以把一个字节拆分成多个使用
# include <stdio.h>
int main() {
// 通过位域,划分结构体 : 后面是指定划分了多少个字节
struct Test {
unsigned int a:1;
unsigned int b:1;
unsigned int c:2;
};
struct Test test;
test.a = 0;
test.b = 1;
test.c = 2;
printf("a = %d, b = %d, c = %d\n", test.a, test.b, test.c); // a = 0, b = 1, c = 2
// size of test = 4
// 只用了四个字节
printf("size of test = %d\n", sizeof(test));
return 0;
}
- 位域成员可以没有名称,只要给出数据类型和位宽即可(因为无名,所以不能直接使用)。
struct Test {
unsigned int x:100;
unsigned int y:200;
unsigned int z:300;
unsigned int :424;
};
位操作
逻辑位运算符(对操作位中的每一位都起作用)
- 按位取反(~),优先级最高
- 按位与(&),优先级第二高
- 按位异或(^),优先级第三高
- 按位或(|),优先级最低
- 和赋值号结合,除了按位取反,其他都可以和赋值号结合
# include <stdio.h>
int main() {
// 十六进制数,以 0x 开头
int mask = 0xFF;
int V1 = 0xABCDEF;
int V2 = 0xABCDEF;
int V3 = 0xABCDEF;
V1 &= mask; // V1 = V1 & mask;
V2 |= mask;
V3 ^= mask;
printf("V1 = 0x%X\n", V1);
printf("V2 = 0x%X\n", V2);
printf("V3 = 0x%X\n", V3);
return 0;
}
移位和位操作的应用(效率很高)
-
左移位运算符 (<<)
- 每次左移一位就相当于 乘以2
- 目标数据 << 移动的位数
- 11001010 << 2 ==》 00101000
-
右移位运算符(>>)
- 每次右移一位就相当于 除以2
- 目标数据 << 移动的位数
-
和赋值号相结合
# include <stdio.h>
int main() {
int value = 1;
while(value < 1024) {
// 每次左移一位就相当于 乘以2 (效率要比直接乘以2要快的多)
value <<= 1; // value = value << 1;
printf("value = %d\n", value);
}
printf("\n...分割线\n");
value = 1024;
while(value > 0) {
// 每次右移一位就相当于 除以2
value >>= 2; // value = value >> 2;
printf("value = %d\n", value);
}
return 0;
}
打开和关闭文件
文本文件 和 二进制文件(除文本文件之外的其他文件)
- 打开和关闭文件***(文件操作完以后以后必须要关闭)***
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp; // 声明文件类型的指针
int ch; // 接收读取的文件
// 用fopen打开文件,并且把指针地址给fp,再判断是不是空
if((fp = fopen("hello_118.txt", "r")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE); // 表示退出程序
}
while((ch = getc(fp)) != EOF) { // EOF 表示文件结束
putchar(ch);
}
// 关闭文件,通过文件指针来进行传递
fclose(fp); // 关闭成功返回非0值,关闭失败返回 EOF
return 0;
}
读写文件1
- 读写单个字符
- 读取:fgetc(函数) 和 getc(宏)
- 写入:fputc 和 putc // 字符,以文本的形式写入
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp1;
FILE *fp2;
int ch;
if((fp1 = fopen("hello_119.txt", "r")) == NULL) {
printf("文件打开失败!\n");
exit(EXIT_FAILURE);
}
if((fp2 = fopen("fishc_119.txt", "w")) == NULL) {
printf("文件打开失败!\n");
exit(EXIT_FAILURE);
}
while((ch = fgetc(fp1)) != EOF) {
// 将 fp1 文件里面的数据写入 fp2 中
fputc(ch, fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
- 读写整个字符串
- 读取:fgets(函数) 和 gets(宏)
- 写入:fputs 和 puts // 字符,以文本的形式写入
# include <stdio.h>
# include <stdlib.h>
# define MAX 1024
int main() {
FILE *fp;
char buffer[MAX];
if((fp = fopen("lines_120.txt", "w")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
fputs("Line one: Hello World! \n", fp);
fputs("Line two: Hello FishC! \n", fp);
fputs("Line one: I love FishC.com! \n", fp);
fclose(fp); // 写入数据完毕以后直接关闭
// 需要读取的时候再次打开
if((fp = fopen("lines_120.txt", "r")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
while(!feof(fp)) { // feop 查找是否到了文件末尾
fgets(buffer, MAX, fp);
printf("%s", buffer);
}
return 0;
}
读写文件2
- 格式化读写文件
- fscanf 和 fprintf // 通过指针控制输入\输出的地方,不在输入到控制台
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
int main() {
FILE *fp;
struct tm *p;
time_t t;
time(&t); // 获取时间值到 t 里面
p = localtime(&t);
if((fp = fopen("date_121.txt", "w")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
fprintf(fp, "%d - %d - %d", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday);
fclose(fp);
int year, month, day;
if((fp = fopen("date_121.txt", "r")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
fscanf(fp, "%d - %d - %d", &year, &month, &day);
printf("%d - %d - %d", year, month, day);
fclose(fp);
return 0;
}
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp;
// wb 二进制写入模式
if((fp = fopen("text_122.txt", "wb")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
fputc('5', fp);
fputc('2', fp);
fputc('0', fp);
fputc('\n', fp);
fclose(fp);
return 0;
}
- fread 和 -fwrite // 以二进制的形式写入
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
struct Date {
int year;
int month;
int day;
};
struct Book {
char name[40];
char author[40];
char publisher[40];
struct Date date;
};
int main() {
FILE *fp;
struct Book *book_for_write, *book_for_read;
book_for_write = (struct Book *)malloc(sizeof(struct Book));
book_for_read = (struct Book *)malloc(sizeof(struct Book));
if(book_for_read == NULL || book_for_write == NULL) {
printf("内存分配失败!\n");
exit(EXIT_FAILURE);
}
strcpy(book_for_write->name, "《带你学C带你飞》");
strcpy(book_for_write->author, "小甲鱼");
strcpy(book_for_write->publisher, "清华大学出版社");
book_for_write->date.year = 2022;
book_for_write->date.month = 3;
book_for_write->date.day = 13;
if((fp = fopen("file_123.txt", "w")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
fwrite(book_for_write, sizeof(struct Book), 1, fp);
fclose(fp);
if((fp = fopen("file_123.txt", "r")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
fread(book_for_read, sizeof(struct Book), 1, fp);
printf("书名:%s\n", book_for_read->name);
printf("作者:%s\n", book_for_read->author);
printf("书名:%s\n", book_for_read->publisher);
printf("出版日期:%d - %d -%d\n", book_for_read->date.year, book_for_read->date.month, book_for_read->date.day);
fclose(fp);
return 0;
}
随机读写文件
- 获取位置指示器的值(ftell)
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp;
if((fp = fopen("hello_124.txt", "w")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
printf("%ld\n", ftell(fp)); //打印出位置指示器的值
fputc('F', fp);
printf("%ld\n", ftell(fp)); //打印出位置指示器的值
fputc('C', fp);
printf("%ld\n", ftell(fp)); //打印出位置指示器的值
fclose(fp);
return 0;
}
-rewind 将位置指示器调整到文件的头部
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp;
if((fp = fopen("hello_124.txt", "w")) == NULL) {
printf("文件打开失败!");
exit(EXIT_FAILURE);
}
printf("%ld\n", ftell(fp)); //打印出位置指示器的值
fputc('F', fp);
printf("%ld\n", ftell(fp)); //打印出位置指示器的值
fputc('C', fp);
printf("%ld\n", ftell(fp)); //打印出位置指示器的值
rewind(fp);
fputs("Hello", fp);
fclose(fp);
return 0;
}
- fseek 找到文件中特定的位置
# include <stdio.h>
# include <stdlib.h>
# define N 4
struct Stu{
char name[24];
int num;
float score;
}stu[N], sb;
int main() {
FILE *fp;
int i;
if((fp = fopen("sorce_125.txt", "wb")) == NULL) {
printf("打开文件失败! \n");
exit(EXIT_FAILURE);
}
printf("请开始录入成绩(格式:姓名 学号 成绩)\n");
for(i = 0; i < N; i++) {
// 字符串不需要加 & ,其他的要加
scanf("%s %d %f", stu[i].name, &stu[i].num, &stu[i].score);
}
fwrite(stu, sizeof(struct Stu), N, fp);
fclose(fp);
if((fp = fopen("sorce.txt", "rb")) == NULL) {
printf("打开文件失败! \n");
exit(EXIT_FAILURE);
}
// 向后移动一个 结构体的位置 这样就可以找到第二个 数据的位置
fseek(fp, sizeof(struct Stu), SEEK_SET);
fread(&sb, sizeof(struct Stu), 1, fp);
printf("%s(%d)的成绩是:%.2f\n", sb.name, sb.num, sb.score);
fclose(fp);
return 0;
}
标准流和错误处理
- 文件流
- 标准输入(stdin)
- 标准输出(stdout)
- 标准错误输出(stderr)
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp;
if((fp = fopen("yagenjiubucunzaidewenjian.txt", "r")) == NULL) {
fputs("文件打开失败!\n", stderr); //标准错误输出(stderr)
exit(EXIT_FAILURE);
}
// do something...
fclose(fp);
return 0;
}
-
重定向
- 重定向标准输入使用 <
- 重定向标准输出使用 >
- 重定向标准错误输出使用 2>
-
错误处理
- 错误指示器 —— ferror
- ferror 只会检测出错了,不显示出了什么错误
IO缓冲区
# include <stdio.h>
# include <stdlib.h>
int main() {
FILE *fp;
if((fp = fopen("output.txt", "w")) == NULL) {
perror("文件打开失败,原因是:");
exit(EXIT_FAILURE);
}
fputs("I love FishC.com!\n", fp);
getchar(); // 主程序,不输入数据程序就会卡在这里
// fputs 输入的数据也会停在缓冲区,不会写入到文件中
fclose(fp);
return 0;
}
- 标准IO提供的三种类型的缓冲模式:
- 按块缓存
- 按行缓存
- 无缓存
# include <stdio.h>
# include <string.h>
int main() {
char buff[1024];
memset(buff, '\0', sizeof(buff));
// _IOFBF 有缓冲区
// _IONBF 没有缓冲区
setvbuf(stdout, buff, _IONBF, 1024);
fprintf(stdout, "Welcome to bbs.fishc.com\n");
fflush(stdout);
fprintf(stdout, "输入任意字符后才会显示该行字符!\n");
getchar();
return 0;
}