我的Github:HIROaaaa
一、C语言常见概念
1.编译器
2.main函数
C语言程序由main函数开始,main 函数是程序的⼊⼝,main 函数也被叫做主函数。int 表⽰ main 函数执⾏结束的时候返回⼀个整型类型的值。
main函数有且仅有一个
3.库函数
C语言标准规定了一组函数,这些函数组成了一个函数库,被称为标准库。
4.关键字
C语言中有一批保留的名字的符号,这些符号被称为保留字或者关键字。
关键字都有特殊的意义,是保留给C语⾔使⽤的
程序员⾃⼰在创建标识符的时候是不能和关键字重复的

5.字符和ASCII编码
6.ASCII码表
1.特殊的几个编码:
7.字符串和\0
8.转义字符
语句和语句分类
C语言的语句可以分为以下五类

1.空语句
2.表达式语句
int main()
{
int a = 20;
int b = 0;
b = a + 5; //表达式语句
return 0;
}
3.函数调用语句
函数调用时会加上分号
int Add(int x, int y)
{
return x+y;
}
int main()
{
printf("hehe\n");//函数调⽤语句
int ret = Add(2, 3);//函数调⽤语句
return 0;
}
4.复合语句
5.控制语句
注释
printf("hello/*这里是注释*/world");
二、数据类型和变量
1.数据类型
1.字符型
char //character
[signed] char //有符号的
unsigned char //⽆符号的
2.整型
//短整型
short [int]
[signed] short [int]
unsigned short [int]
//整型
int
[signed] int
unsigned int
//⻓整型
long [int]
[signed] long [int]
unsigned long [int]
//更⻓的整型
//C99中引⼊
long long [int]
[signed] long long [int]
unsigned long long [int]
3.浮点型
float
double
long double
4.布尔类型
_Bool
#define bool _Bool
#define false 0
#define true 1
运用:
_Bool flag = true;
if (flag)
printf("i like C\n");
5.signed 和 unsigned
unsigned a;
signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为 0 到 255
6.数据类型的表示
7.各种数据类型的长度
2.变量
类型是⽤来创建变量的
1.变量的存储
内存有三个区域:栈区、堆区、静态区
2.科学计数法

3.占位符
1.占位符列举

%c不忽略空白字符(空格、换行符、制表符),可以通过scanf(" %c", &ch)来跳过字符前的空白字符
%s是从第一个非空白字符开始到第一个空白字符结束,所以单个%s无法读取有空格的字符串,即scanf不适合读取有空格的字符串
scanf读取字符串后会在字符串末尾加一个"\0"
2.printf限定输出宽度
int main()
{
printf("%5d\n", 123); // 输出为 " 123"
return 0;
}
%5代表限制输出的整型的宽度最少为5位,不满5位会自动在右侧补空格
对于⼩数,这个限定符会限制所有数字的最⼩显⽰宽度
// 输出 " 123.450000"
#include <stdio.h>
int main()
{
printf("%12f\n", 123.45);
return 0;
}
输出的数字中的'.'也被算作是1位!
int main()
{
printf("Number is %.5f\n", 0.5123451);
return 0;
}
//输出“0.51234”
限制小数位数可以和限制输出字符串宽度一起使用
#include <stdio.h>
int main()
{
printf("%6.2f\n", 0.5);
return 0;
}
//输出“ 0.50”
类似的,还可以限制字符串的输出("%.[m]s")
// 输出 hello
#include <stdio.h>
int main()
{
printf("%.5s\n", "hello world");
return 0;
}
但是此时不能与限制字符串宽度一起使用
限制字符串宽度和限制小数位数可以用'*'代替从而做到输入想要限制的宽度和位数
printf("%*.*f\n", 6, 2, 0.5);
//相当于
printf("%6.2f\n", 0.5);
3.printf输出总是显示正负
默认printf输出时只有负数会加'-'而正数不会加'+',可以通过在'%'加一个 '+'来解决
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
4.scanf用法
int main()
{
int x;
float y;
// ⽤⼾输⼊ " -13.45e12# 0"
scanf("%d", &x);
printf("%d\n", x);
scanf("%f", &y);
printf("%f\n", y);
return 0;
}
上面示例中,第一个scanf要读取整型数据,所以只读到-13,遇到'.'后因为不合条件停止,第二个scanf读取浮点型,会从之前没有读完的数据开始读取,读取到.45e12,后面的#不合条件,读取会停止
int main()
{
int x;
float y;
// ⽤⼾输⼊ " -13.45e12# 0"
scanf("%d%f", &x, &y);
return 0;
}
1.返回值
scanf返回值是一个整数,表示成功读取到的变量的数量
没有读取到变量或者匹配失败则返回0
没有读取到变量之前,读取错误或者读取到文件结尾,返回EOF(-1)(EOF - end of file ⽂件结束标志)
程序运行时按“ctrl+z”会提前结束输入
scanf也可以限制字符串读取的最大长度(%[m]s)
#include <stdio.h>
int main()
{
char name[11];
scanf("%10s", name);
return 0;
}
2.赋值忽略符
输入不符合预定的字符时,赋值忽略符可‘*’以使该位置的输入被丢弃
#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d%*c%d%*c%d", &year, &month, &day);
return 0;
}
比如上面的例子中输入年份可能是2025.11.8也可能是2025-11-8,加入赋值忽略符就可以避免数据解析失败
三、分支和循环
1.if语句
目前跟人感觉无特别需要注意的点
2.switch 语句
3.while循环
无
4.for循环
无
5.do while循环
无
6.break和continue语句
break只终止其所在的那一层循环
7.goto语句
8.随机数生成
rand
需要包含头文件<stdlib.h>
srand
time
四、数组
1.基本要点
2.数组在内存中的存储
3.sizeof 计算数组元素个数
#include <stido.h>
int main()
{
int arr[10] = {0};
printf("%d\n", sizeof(arr));
return 0;
}
4.二维数组
二维数组就是把一位数组作为数组的元素

定义二位数组的方法:
int arr[3][5];
int arr[][5];
5.变长数组
int n = a+b;
int arr[n];
VS不行!!!(但是有办法修改成可以的,方法可以自己搜,很简单)
五、函数
1.概念
2.库函数
库函数使用方法可以在以下网站查询:
3.形参和实参
1.实参
2.形参
4.return 语句
return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式
5.函数的嵌套和链式访问
1.嵌套
2.链式访问
6.函数的声明和定义
函数的定义也是⼀种特殊的声明
多个文件
7.static 和 extern
1.static
2.extern
8.函数的注意事项
六、函数递归
1.递归
int Fact(int n)
{
if(n==0)
return 1;
else
return n*Fct(n-1);
}
2.递归与迭代
int Fact(int n)
{
int i = 0;
int ret = 1;
for(i=1; i<=n; i++)
{
ret *= i;
}
return ret;
}
这种方法计算!n的效率比递归的方法要更好
七、操作符(待补充)
1.单目操作符
sizeof
int main()
{
short s = 2;
int b = 10;
printf("%d\n", sizeof(s = b+1));
printf("s = %d\n", s);
return 0;
}
这里输出
2.成员访问操作符("."和"->")(待补充)
1.访问结构体的成员给其赋值
struct Student
{
char name[20];
int age;
}; //这里的分号不能漏掉!!
//结构体的声明
//struct tag
//{
// member-list; 成员列表
//}variable-list; 创建全局的结构体变量
//相当于
struct Student s7;
int main()
{
struct Student s1={"hiro",18}; //创建一个结构体变量(局部的)
struct Student s2={.name="hiro",.age=18} // 可以通过 '.'来先找到结构体的成员然后给他赋值 (顺序可以打乱)
}
2.结构体可以嵌套
嵌套结构中用"{a,b,c}"给被嵌套的结构体中的结构体赋值
struct A
{
char a;
int b;.
};
struct Student
{
struct A a;
char name[20];
int age;
};
int main()
{
struct Student s1={{'a',100},"hiro",18};//字符串要用""单个字符用''
}
3.打印结构体中的成员
同样使用成员访问操作符打印
格式:
结构体变量.成员名
printf("%s %d", s1.name, s1.age);
4.寻找/打印嵌套的结构体中的成员
两次寻找
printf("%c %d", s1.a.m,s1.a.n);
5.结构体成员的间接访问 (指针)
3.强制类型转换
int a = 3.14;
//a的是int类型, 3.14是double类型,两边的类型不⼀致,编译器会报警告
int a = (int)3.14;
//意思是将3.14强制类型转换为int类型,这种强制类型转换只取整数部分
4.关系操作符
i < j < k
1 (i < j) < k
1 i < j && j < k
5.条件操作符(三目操作符)
exp1 ? exp2 : exp3
如果exp1为真,则结果exp2整个表达式的结果,否则为exp3的结果
6.逻辑操作符(&& , || , !)
1.逻辑取反
#include <stdio.h>
int main()
{
int flag = 0;
if(!flag)
{
printf("do something\n");
}
return 0;
}
2.逻辑与
3.逻辑或
只要有一边为真就为真
从左往右判断,如果左边已经是真,则直接为真
7.原码、反码、补码
8.移位操作符
num>>-2
1.左移操作符
#include <stdio.h>
int main()
{
int num = 10;
int n = num<<1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
2.右移操作符
#include <stdio.h>
int main()
{
int num = 10;
int n = num>>1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
9.位操作符
10.逗号表达式
exp1, exp2, exp3, …expN
11.下标访问[]、函数调⽤()
arr[1] 相当于 *(arr+1) []就是对arr解引用
12.操作符的属性:优先级、结合性
1.优先级
包含多个运算符的表达式中,哪个运算符应该先执行
比如'*'的优先级高于'+'
2.结合性
优先级相同的操作符之间执行的顺序
比如
a&&b&&c||d
从左至右依次执行: a&&b (a&&b)&&c [(a&&b)&&c]||d
3.优先级与结合性汇总

13.表达式求值
1.整型提升
在进行整型运算时,表达式中的字符和短整型(类型长度小于int的)操作数在使用之前会先被转换为普通整型
例如
char a=3;
char b=127;
char c=a+b;
printf("%d",c);
计算 a+b过程中
要先将char类型的a和b转化为int 类型再进行计算 这时计算精度会提高
1.提升方法
2.有符号
常见编译器中char一般都是有符号的char
按照变量的数据类型的符号位来提升
例如上面的例子
a为 00000011
b为 01111111
a和b会被提升为整型并且按照符号位即最前面的0来提升
00000000000000000000000000000011 -a
00000000000000000000000001111111 -b
相加得
00000000000000000000000010000010
而c为char类型,只有8个比特位,所以c最后为
10000010 -c
打印时也会发生整型提升
c按照符号位'1'来提升
11111111111111111111111110000010 -c(补码)
打印原码
10000000000000000000000001111110 --原码
所以打印出'-126'
总结:打印时先将c提升,提升后的是c的补码,但是打印的是原码 (计算用补码,只有打印时用原码)
3.无符号
高位补0
2.算术转换
计算时,如果操作符的操作数类型不同,会先由小的类型转化为大的类型,再进行计算

注意 :这里已经不包括前面的整型提升
问题表达式的解析
八、指针
1.指针变量
1.解引用操作符
“*” 解引用操作符通过地址来访问变量,通过地址找到指向的空间
2.指针变量的大小
32位平台4字节 64位8字节
3.指针变量类型的意义
4.指针+-整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。
5.野指针
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
p=NULL;
来避免使用野指针
一般在这个指针用不到了之后这么做
6.二级指针
存放指针变量的地址的指针是二级指针
int b = 20;
int*pa;
int*ppa = &b;//等价于pa=&b;
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
7.字符指针变量
char*是字符指针
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还可以这么用:
int main()
{
const char* pstr = "hello bit.";
printf("%s\n", pstr);
return 0;
}
这里并不是把整个字符串放在了字符指针里,而是把字符串首元素的地址给了字符指针
8.数组指针变量
1.定义
指向数组的指针,存放的是数组的地址
int (*p)[10];
2.数组指针的初始化
int arr[10] = {0};
int(*p)[10] = &arr;
int (*p) [10] = &arr;
2 | | |
3 | | |
4 | | p指向数组的元素个数
5 | p是数组指针变量名
6 p指向的数组的元素类型
3.⼆维数组传参的本质
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
test(arr, 3, 5);
void test(int (*p)[5], int r, int c)
|
|
指向一个有五个元素的一维数组的指针
9.函数指针变量
int (*pf3) (int x, int y)
| | ------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
//输出
//5
//8
其实就是把函数或者数组的名字换成了指针变量的名字
10.typedef在指针定义时的运用
typedef unsigned int uint;
typedef int * ptr_t;
然后将其运用于数组指针和函数指针:
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
typedef void(*pun_t)(int, int);
#include <stdio.h>
typedef int(*arr_t)[5];
typedef int(*pfun_t)(int, int);
int sum(int x, int y)
{
return x + y;
}
int main()
{
//函数
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
pfun_t pfun = sum;
int result = pfun(a, b);
printf("%d", result);
//数组
int arr[5] = {1,2,3,4,5};
arr_t parr = &arr;
for (int i = 0; i < 5; i++)
{
printf("%d", (*parr)[i]); //要注意[]优先级大于*,如果写成parr[i]的话parr指向的是整个数组
}
return 0;
}
下面这段代码就可以用简化的方式来写:
void (*signal(int , void(*)(int)))(int);
简化:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
2.void*指针
3.const修饰指针
const int* p=0;
*p = 20; //no
p = &m; //ok
int * const p=&n;
*p = 20; //ok
p = &m; //no
4.assert断言
assert(p != NULL);
表达式为真返回非0,为假返回0并报错,在标准错误流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号
#define NDEBUG
#include <assert.h>
在VS这样的集成开发环境中,assert在relese模式中会自动优化掉
5.数组中指针的运用
1.指针访问数组
#include <stdio.h>
int main()
{
int arr[10] = {0};
1
2
3
4
比特就业课主页:https://m.cctalk.com/inst/s9yewhfr 比特就业课
//输⼊
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//输⼊
int* p = arr;
for(i=0; i<sz; i++)
{
scanf("%d", p+i);
//scanf("%d", arr+i);//也可以这样写
}
//输出
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
数组名就是首元素地址,所以可以把数组名直接赋值给指针变量p,让指针变量p指向数组,这时,数组名arr和p是等价的
arr[i]相当于*(arr+i) 同样p[i]相当于*(p+i)
一维数组传参可以写成数组的形式也可以写成指针的形式
2.指针数组
指针数组是数组,是存放指针的数组

3.指针数组模拟⼆维数组
#include <stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = {arr1, arr2, arr3};
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}

6.函数指针数组
存放函数的地址的数组
int (*parr1[3])(int,int);
上面的定义指的是创建一个存放整型类型的数组,这个数组的元素是有两个int类型参数的函数的地址

7.转移表
转移表就是利用函数指针数组,将多个函数放在一个数组中来实现一定的功能
例如用转移表实现计算器,可以参考:转移表实现简易计算器-优快云博客
8.回调函数
#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
qsort使用时需要一个比较函数,这个函数决定了排序的方式是逆序还是正序,是比较整型还是比较字符
struct Stu //学⽣
{
char name[20];//名字
int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test2();
test3();
return 0;
}
九、字符函数和字符串函数
1.字符分类函数
用来给每个字符分类的类型分类的函数,使用需要头文件<ctype.h>

int a = islower ( int c );
是小写字母返回非0,不是返回0
2.字符转换函数
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写
3.字符串函数及其模拟实现
头文件<string.h>
1.strlen
size_t strlen ( const char * str );
遇到字符串结尾‘\0’结束,参数指向的字符串必须以'\0'结尾
返回值类型size_t是无符号的
模拟实现:
#include <stdio.h>
//1.用循环找\0
int my_strlen(const char* str)
{
int len = 0;
for (int i = 0; str[i] != '\0'; i++)
len++;
return len;
}
//2.用地址
int my_strlen(const char* str)
{
int len = 0;
char *p = str;
while (*p != '\0')
p++;
len = p - str;
return len;
}
//3.不创建计数变量,用递归
int my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1+my_strlen(str + 1);
}
2.strcpy
复制一个字符串的内容到另一个字符串
char* strcpy(char * destination, const char * source );
源字符串必须以'\0'结尾 会将字符串中的'\0'拷贝
目标空间必须足够大
模拟实现:
#include <stdio.h>
char* my_strcpy( char* str2,const char* str1)
{
char* ret = str2;
while ((*str2++ = *str1++))
{
;
}
return ret;
}
3.strcat
在一个字符串后面追加字符串
⽬标字符串中也得有 \0 ,否则没办法知道追加从哪⾥开始
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}
4.strcmp
比较两个字符串的大小
int my_strcmp (const char * str1, const char * str2)
{
int ret = 0 ;
assert(str1 != NULL);
assert(str2 != NULL);
while(*str1 == *str2)
{
if(*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1-*str2;
}
5.strncpy
限定长度的复制
char * strncpy ( char * destination, const char * source, size_t num );
char * strncat ( char * destination, const char * source, size_t num );
int strncmp ( const char * str1, const char * str2, size_t num );
char * strstr ( const char * str1, const char * str2);
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="This is a simple string";
char * pch;
pch = strstr (str,"simple");
strncpy (pch,"sample",6);
printf("%s\n", str);
return 0;
}
模拟实现:strstr的模拟实现-优快云博客
6.strtok
找到str中的下一个标记,并将其用‘\0’结尾,返回一个指向这个标记的指针
char * strtok ( char * str, const char * sep);
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "192.168.6.111";
char* sep = ".";
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
7.strerror
char* strerror ( int errnum );
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");
if (pFile == NULL)
perror("Error opening file unexist.ent");
return 0;
}
//输出:Error opening file unexist.ent: No such file or directory
十、内存操作函数
1.memcpy
void * memcpy ( void * destination, const void * source, size_t num );
void* my_memcpy(void* des, void* sou, size_t num)
{
assert(des);
assert(sou);
void* ret = des;
while (num--)
{
*(char*)des = *(char*)sou;
des=(char*)des+1;
sou=(char*)sou+1;
}
return ret;
}
2.memmove
void * memmove ( void * destination, const void * source, size_t num );
3.memset
void * memset ( void * ptr, int value, size_t num );
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "hello world";
memset (str,'x',6);
printf(str);
return 0;
}
//输出:
//xxxxxxworld
4.memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
十一、数据在内存中的存储
1.大小端
2.浮点数在内存中的存储
1.基本概念
2.存储过程
3.提取过程
十二、自定义类型
1.结构体
1.结构体的声明
struct tag
{
member-list;
}variable-list;
2.结构体变量的创建和初始化
#include <stdio.h>
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
//按照结构体成员的顺序初始化
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex =
"⼥" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
return 0;
}
3.结构的特殊声明结构的特殊声明
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
4.结构的⾃引⽤
struct Node
{
int data;
struct Node* next;
};
作用:通过一个结构体去访问另一个结构体内的数据
例如:
#include <stdio.h>
// 定义自引用结构体(链表节点)
struct Node {
int data; // 数据域:存储整数
struct Node* next; // 自引用指针:指向另一个Node节点
};
int main()
{
// 1. 创建第二个节点(被指向的节点)
struct Node node2 = {20, NULL}; // next=NULL表示没有下一个节点
// 2. 创建第一个节点,next指向node2(自引用的核心用法)
struct Node node1 = {10, &node2};
// 3. 输出第一个节点的data,以及它指向的第二个节点的data
printf("node1.data = %d\n", node1.data);
// 通过node1.next访问第二个节点的data
printf("node1.next->data = %d\n", node1.next->data);
return 0;
}
5.结构体内存对齐
#pragma pack(1)//设置默认对⻬数为1
#pragma pack()//取消设置的对⻬数,还原为默认
6.结构体传参
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
传地址更好!传参时参数需要压栈,会有时间和空间上的系统开销
7.结构体实现位段
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
//正确的⽰范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}
2.联合体
1.联合体的声明
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = {0};
// 下⾯输出的结果是⼀样的吗?
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
printf("%p\n", &un);
return 0;
}
输出:
001AF85C
001AF85C
001AF85C
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = {0};
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
输出:
11223355
可以看到,第四个字节的内容被更改了,可以画出图:
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;//返回1是⼩端,返回0是⼤端
}
2.联合体的大小
struct gift_list
{
int stock_number;//库存量
double price; //定价
int item_type;//商品类型
union {
struct
{
char title[20];//书名
char author[20];//作者
int num_pages;//⻚数
}book;
struct
{
char design[30];//设计
}mug;
struct
{
char design[30];//设计
int colors;//颜⾊
int sizes;//尺⼨
}shirt;
}item;
};
这样,就减少了两个物品独有的内容的大小
3.枚举类型
1.枚举类型的声明
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Color
{
RED=2,
GREEN=4,
BLUE=8
};
2.枚举类型的优点
3.枚举类型的使用
enum Color//颜⾊
2 {
3 RED=1,
4 GREEN=2,
5 BLUE=4
6 };
7
8 enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值
C语言中可以拿整数给枚举变量赋值,但是C++中不行,C++的类型检查比较严格
十三、动态内存管理
1.malloc和free
1.malloc
void * malloc ( size_t size);
2.free
void free ( void * ptr);
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int arr[num] = {0};
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
if(NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//是否有必要?
return 0;
}
ptr要指向起始位置,ptr最好不要改变
3.calloc
void * calloc ( size_t num, size_t size);
4.realloc
void * realloc ( void * ptr, size_t size);
2.常⻅的动态内存的错误
3.柔性数组
struct st_type
{
int i;
int a[0];//柔性数组成员
};
struct st_type
{
int i;
int a[];//柔性数组成员
};
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
return 0;
}
优势:
#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
int i;
int *p_a;
}type_a;
int main()
{
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
return 0;
}
1.⽅便内存释放
4.C/C++中程序内存区域划分
十四、文件操作
1.文件的分类
2.流和标准流
3.⽂件指针
FILE* pf;
4.文件的打开和关闭
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );

int main ()
{
FILE * pFile;
//打开⽂件
pFile = fopen ("myfile.txt","w");
//⽂件操作
if (pFile!=NULL)
{
fputs ("fopen example",pFile);
//关闭⽂件
fclose (pFile);
}
return 0;
}
5.文件的读写
1.顺序读写
scanf —— 从标准输入(键盘)读取
fscanf —— 从文件流读取
int fscanf(FILE *stream, const char *format, ...);
sscanf —— 从字符串读取
int sscanf(const char *str, const char *format, ...);
#include <stdio.h>
int main() {
char src[] = "name=Tom age=18 score=95.5";
char name[10];
int age;
float score;
// 按格式解析字符串(忽略固定字符 "name=" " age=" " score=")
int ret = sscanf(src, "name=%s age=%d score=%f", name, &age, &score);
printf("读取个数:%d\n", ret);
printf("name=%s, age=%d, score=%.1f\n", name, age, score);
return 0;
}
上面的函数返回的都是读取的数据的个数,printf也是
2.随机读写
1.fseek
根据⽂件指针的位置和偏移量来定位⽂件指针(⽂件内容的光标)。
int fseek ( FILE * stream, long int offset, int origin );偏移量 起始位置
int main ()
{
FILE * pFile;
pFile = fopen ( "example.txt" , "wb" );
fputs ( "This is an apple." , pFile );
fseek ( pFile , 9 , SEEK_SET );
fputs ( " sam" , pFile );
fclose ( pFile );
return 0;
}
SEEK_SET 文件起始位置
SEEK_CUR 文件指针当前位置
SEEK_END 文件末尾
2.ftell
返回⽂件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
#include <stdio.h>
int main ()
{
FILE * pFile;
long size;
pFile = fopen ("myfile.txt","rb");
if (pFile==NULL)
perror ("Error opening file");
else
{
fseek (pFile, 0, SEEK_END); // non-portable
size=ftell (pFile);
fclose (pFile);
printf ("Size of myfile.txt: %ld bytes.\n",size);
}
return 0;
}
3.rewind
void rewind ( FILE * stream );
#include <stdio.h>
int main ()
{
int n;
FILE * pFile;
char buffer [27];
pFile = fopen ("myfile.txt","w+");
for ( n='A' ; n<='Z' ; n++)
fputc ( n, pFile);
rewind (pFile);
fread (buffer,1,26,pFile);
fclose (pFile);
buffer[26]='\0';
printf(buffer);
return 0;
}
3.⽂件读取结束的判定
1.被错误使⽤的 feof
在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束
⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,⾮char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
读取或者写入出错时,会创建一个标记,这个标记可以被ferror识别到
识别到是读取或者写入错误ferror才返回真
4.文件缓冲区

fflush(pf);
十五、编译和链接
1.翻译环境和运⾏环境
2.翻译环境
1.大体过程

从.c文件到.obj文件的过程是编译,.obj文件和链接库到link.exe文件的过程是链接
多个.c文件生成可执行程序:

2.预处理(预编译)
1.简述
gcc -E test.c -o test.i
C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
printf("file:%s line:%d\n", __FILE__, __LINE__);
2.#define 定义常量
# define name stuff
#define MAX 1000
#define reg register //为 register这个关键字,创建⼀个简短的名字
#define do_forever for(;;) //⽤更形象的符号来替换⼀种实现
#define CASE break;case //在写case语句的时候⾃动把 break写上。
// 如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏
符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
可以使用‘\’(行续符)来让预处理器把其链接的代码视作一个完整的宏定义(只对宏生效)
#define定义时不要加上';',会引起两个分号的的问题
3.#define定义宏:
# define name( parament-list ) stuff
1 #define SQUARE( x ) x * x
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
要注意这里的替换是单纯的替换,替换执行的优先级高于运算符的优先级,所以最好在每一项上加上()来确保其优先级正确
#define DOUBLE( x) ( ( x ) + ( x ) )
x+1;//不带副作⽤
x++;//带有副作⽤
#include <stdio.h>
// 定义一个宏:把 NUM 替换成 100
#define NUM 100
// 定义一个宏:把 STR 替换成 "hello"
#define STR "hello"
int main() {
// 场景1:代码中的 NUM/STR(不在字符串里)→ 正常替换
int a = NUM; // 预编译后:int a = 100;
char *s1 = STR; // 预编译后:char *s1 = "hello";
// 场景2:字符串里的 NUM/STR(在""内)→ 完全不替换
char *s2 = "NUM = 100"; // 预编译后还是:char *s2 = "NUM = 100";
char *s3 = "STR is test";// 预编译后还是:char *s3 = "STR is test";
printf("%d\n", a); // 输出 100
printf("%s\n", s1); // 输出 hello
printf("%s\n", s2); // 输出 NUM = 100(不是 100 = 100)
printf("%s\n", s3); // 输出 STR is test(不是 hello is test)
return 0;
}
4.宏函数的对⽐
# define MAX(a, b) ((a)>(b)?(a):(b))
#define MALLOC(num, type)\
(type )malloc(num sizeof(type))
...
//使⽤
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 sizeof(int));
5.#运算符
# define PRINT(n) printf( "the value of " #n " is %d" , n);
printf("the value of ""a" " is %d", a);
6.## 运算符:
//宏定义
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
//调⽤函数
int m = int_max(2, 3);
printf("%d\n", m);
float fm = float_max(3.5f, 4.5f);
printf("%f\n", fm);
return 0;
}
##就是替换函数外面的函数名(变量名 / 函数名 / 类型名等),把其和其他的字符变成一个标识符,#是替换函数内的变量名
7.命名约定
8.#undef
这条指令⽤于移除⼀个宏定义
# undef NAME// 如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。
9.命令⾏定义
许多C 的编译器提供了⼀种能⼒,允许在命令⾏中定义符号。⽤于启动编译过程
#include <stdio.h>
int main()
{
int array [ARRAY_SIZE];
int i = 0;
for(i = 0; i< ARRAY_SIZE; i ++)
{
array[i] = i;
}
for(i = 0; i< ARRAY_SIZE; i ++)
{
printf("%d " ,array[i]);
}
printf("\n" );
return 0;
}
//linux 环境演⽰gcc -D ARRAY_SIZE= 10 programe.c
#define)定义宏
10.条件编译
#include <stdio.h>
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
11.头⽂件的包含
#include“xxxx.h”是先从源文件目录中找头文件,找不到再从库文件里找
#include<xxxx.h>是直接从库文件里找头文件,不会从源文件目录里找,所以不能用这个来包含自定义的头文件
12.嵌套⽂件包含
可以通过条件编译的方法来避免
在开头写
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
或者是c语言自带的方法
在开头写
#pragma once
3.编译
gcc -S test.i -o test.s
array [index] = (index+ 4 )*( 2 + 6 );
1.词法分析
2.语法分析
3.语义分析
4.汇编
gcc -c test.s -o test.o
3137



被折叠的 条评论
为什么被折叠?



