目录
1 初识C语言(1)
国际语言标准:ANSIC -C89/90
1.1 输入函数
scanf("%d%d", &a, &b); //& - 取地址符号
1.2 输出函数
printf("%d\n", sum);
%d | 打印十进制有符号数 |
%c | 打印字符 |
%f | 打印浮点型 |
%p | 以地址形式打印 |
%x | 打印16进制数字 |
%s | 打印字符串 |
%zd | 打印内存大小 |
%u | 打印十进制无符号数 |
1.3 计算机中的单位
bit - 比特位 | 只能表示 0 1 |
byte - 字节 | 1byte = 8bit |
kb | 1kb = 1024byte |
mb | 1mb = 1024kb |
gb | 1gb = 1024mb |
tb | 1tb = 1024gb |
pb | 1pd = 1024tb |
1.4 数据类型
char | 字符型 | 1字节 | 0 ~ 2^8 - 1 |
short | 短整型 | 2字节 | 0 ~ 2^16 - 1 |
int | 整型 | 4字节 | 0 ~ 2^32 - 1 |
long | 长整型 | 4/8字节 | 0 ~ 2^64 - 1 |
long long | 更长的整形 | 8字节 | 0 ~ 2^64 - 1 |
float | 单精度浮点数 | 4字节 | 0 ~ 2^32 - 1 |
double | 双精度浮点数 | 8字节 | 0 ~ 2^64 - 1 |
long(根据编译器不同,字节也不同,C语言标准规定 sizeof(long) >= sizeof(int) )
1.5 变量分类
1.5.1 局部变量
作用域:变量所在的局部范围(只在{ }中有效)
生命周期:进入作用域生命周期开始,出作用域生命周期结束
1.5.2 全局变量
作用域:整个工程(一个c文件中的全局变量可以在另一个c文件中使用,要用extern声明)
生命周期:整个程序的生命周期(main函数执行结束)
2 初识C语言(2)
https://www.cnblogs.com/liuyangfirst/p/16490769.html中的检查设置
2.1 常量
1.字面常量
2.const 修饰的常变量
3.#define 定义的标识符常量
4.枚举常量
#include <stdio.h>
#define A 1; #define定义的标识符常量
//枚举常量
enum Sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
1; //字面常量
const int a = 1; //const修饰的常变量
return 0;
}
2.2 字符串
字符串的结束标志是一个 \0 的转义字符。 在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。
#include <stdio.h>
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a','b','c','\0'};
printf("%d\n", strlen(arr1)); // 3
printf("%d\n", strlen(arr2)); // 3
return 0;
}
2.3 转义字符
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符 |
\t | 水平制表符 |
\' | 用于表示字符常量 ‘ |
\" | 用于表示一个字符串内部的双引号 |
\n | 换行 |
\ddd | ddd表示1~3个八进制的数字。如:\130X |
\xdd | dd表示2个十六进制数字。如:\x30 |
#include <stdio.h>
int main()
{
printf("%d\n", strlen("\132")); //1
printf("%c\n",'\132'); //Z
// \132 -- 32是2个8进制数字
// 132作为8进制代表的那个十进制数字,代表ASCII码值,对应的字符
// 132 --> 八进制转十进制 90 --> ASCII码值 Z
return 0;
}
转义字符长度为1
3 初识C语言(3)
3.1 操作符
& 取地址
* 解引用
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a; //取地址 int* 指针变量,用来存放地址的
printf("%p\n", &a);
printf("%p\n", p);
*p = 20; // * 解引用操作符
printf("%d\n", a); //20
return 0;
}
3.2 常见关键字
auto
auto int a = 1; //局部变量 - 自动变量
register
//建议把a定义成寄存器变量,不一定能放到寄存器里,因为寄存器数量有限,能不能放取决于编译器
register int a = 1;
typedef
typedef unsigned int u_int; //类型定义 - 类型重定义
unsigned int a = 1;
u_int b = 2;
static
1.static修饰局部变量,局部变量的生命周期变长。
2.static修饰全局变量,改变了变量的作用域 - 让静态的全局变量只能在自己所在的源文件内部使用,出了源文件就没法使用了。
3.static修饰函数,改变了函数的链接属性,一个没有static修饰的函数具有外部链接属性,也就是可以在其他源文件使用,只要用extend声明就可以使用;但是static修饰的函数变成了内部链接属性,只能在自己所在的源文件使用。
4 分支和循环(1)
else与最近的没有匹配的if进行匹配
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if (1 == a)
if (2 == b)
printf("aa");
else
printf("bb");
return 0; //什么都没输出
}
case(整形常量表达式)
continue是用于终止本次循环,也就是本次循环中continue后面的代码不会再执行。
5 分支和循环(2)
看 0:14:00
#include <stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
for循环的判断部分如果被省略,那判断条件就是:恒为真。
#include <stdio.h>
int main()
{
//死循环
for (;;) {
printf("hello\n");
}
return 0;
}
6 函数(1)
c语言参考文档:https://www.cplusplus.com
6.1 函数调用
传值调用:对形参的修改是不会改变实参的。
传址调用:函数内部可以直接操作函数外部的变量。
#include <stdio.h>
//传值
void swap1(int pa, int pb) {
int temp = 0;
temp = pa;
pa = pb;
pb = temp;
}
//传址
void swap2(int* pa, int* pb) {
int temp = 0;
temp = *pa;
*pa = *pb;
*pb = temp;
}
int main()
{
int a = 10;
int b = 20;
printf("%d,%d\n", a, b); //10,20
swap1(a, b);
printf("%d,%d\n", a, b); //10,20
swap2(&a, &b);
printf("%d,%d\n", a, b); //20,10
return 0;
}
#include <stdio.h>
int binarySearch(int arr[], int k, int len) {
int left = 0;
//错误:这里arr传的是首元素的地址,所以x64地址占8个字节,sizeof(arr) = 8
//只能从外面传len
//int len = sizeof(arr) / sizeof(arr[0]);
int right = len - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] > k) {
right = mid - 1;
}
if (arr[mid] < k) {
left = mid + 1;
}
if (arr[mid] == k) {
return mid;
}
}
return 0;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int len = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", binarySearch(arr, 8, len));
return 0;
}
#include <stdio.h>
void add(int* num) {
//*num++; //错误: ++ 比 * 优先级高
(*num)++;
}
int main()
{
int num = 0;
add(&num);
add(&num);
add(&num);
printf("%d\n", num); //3
return 0;
}
6.2 函数链式访问
#include <stdio.h>
int main()
{
//printf返回值是:打印的字符的个数
printf("%d\n", printf("%d", printf("%d", 43))); //4321
return 0;
}
6.3 函数定义和声明
函数定义:函数的定义是指函数的具体实现,交代函数的功能实现。
函数声明:函数的声明一般要放在头文件中。
// add.c 函数定义
void add(int* num) {
(*num)++;
}
//防止头文件被重复引用多次
#ifdef _ADD_H_
#define _ADD_H_
// add.h 函数声明
void add(int* num);
#endif // _ADD_H_
#include <stdio.h>
#include "add.h"
int main()
{
int num = 0;
add(&num);
add(&num);
add(&num);
printf("%d\n", num); //3
return 0;
}
7 函数和递归(2)
国外程序员的知乎:https://stackoverflow.com
7.1 递归的两个必要条件
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
迭代(循环)可以用递归替换。
8 数组(1)
8.1 一维数组
一维数组定义:类型名 数组名[整形常量表达式]
一维数组在内存存储是连续的
#include <stdio.h>
int main()
{
//int n = 10;
//int arr[n] = { 0,1,2,3,4,5,6,7,8,9 }; //错误:n是变量
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int len = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < len; i++) {
printf("%p\n", &arr[i]);
}
return 0;
}
//相差4个字节
//000000722E0FF888
//000000722E0FF88C
//000000722E0FF890
//000000722E0FF894
//000000722E0FF898
//000000722E0FF89C
//000000722E0FF8A0
//000000722E0FF8A4
//000000722E0FF8A8
//000000722E0FF8AC
8.2 二维数组
二维数组在内存存储也是连续的
#include <stdio.h>
int main()
{
//3行4列,行可以省略,列不能省略
int arr1[3][4] = { {1,2},{3,4,5} ,{6,7,8,9} };
int arr2[][4] = { {1,2},{3,4,5} ,{6,7,8,9} };
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%p\n", &arr2[i][j]);
}
printf("\n");
}
return 0;
}
//0000000399CFF8D8
//0000000399CFF8DC
//0000000399CFF8E0
//0000000399CFF8E4
//
//0000000399CFF8E8
//0000000399CFF8EC
//0000000399CFF8F0
//0000000399CFF8F4
//
//0000000399CFF8F8
//0000000399CFF8FC
//0000000399CFF900
//0000000399CFF904
9 数组(2)
9.1 数组名
数组名是首元素地址
例外:
1.sizeof(数组名) - 数组名表示整个数组
2. &数组名 - 数组名代表整个数组,取出的是整个数组的地址
#include <stdio.h>
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
printf("%p\n", arr); //000000C7884FF928
printf("%p\n", &arr[0]); //000000C7884FF928
printf("%d\n", *arr); //9
printf("%p\n", &arr); //000000C7884FF928 相同的原因是首元素地址就是作为整数组的地址
return 0;
}
以下验证 &数组名 取出的是整个数组的地址
#include <stdio.h>
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//加1 仅加4个字节 说明是首元素地址
printf("%p\n", arr); //000000DAAB5DFA88
printf("%p\n", arr+1); //000000DAAB5DFA8C
printf("%p\n", &arr[0]); //000000DAAB5DFA88
printf("%p\n", &arr[0]+1); //000000DAAB5DFA8C
//加1 加 4 * 10 个字节 说明是整个数组地址
printf("%p\n", &arr); //000000DAAB5DFA88
printf("%p\n", &arr+1); //000000DAAB5DFAB0
return 0;
}
10 C语言操作符详解(1)
10.1 >> 算术右移操作符
#include <stdio.h>
int main()
{
int a = 1;
int b = -1;
//整数的二进制表示有:原码、反码、补码
// 正数的原码、反码、补码都一样
// 存储到内存的是补码
// 10000000 00000000 00000000 00000001 - 原码
// 11111111 11111111 11111111 11111110 - 反码 原码符号位不变,其他按位取反得反码
// 11111111 11111111 11111111 11111111 - 补码 反码+1得补码
// 11111111 11111111 11111111 11111111 - b >> 1 移的是补码,高位补符号位,所以还是-1
printf("%d\n",a >> 1); //0
printf("%d\n", b >> 1); //-1
return 0;
}
10.2 & 按位与
两个同时为1才为1
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a & b;
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 00000101
//00000000 00000000 00000000 00000001
printf("%d\n", c); //1
return 0;
}
10.3 | 按位或
只要一个为1就为1
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a | b;
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 00000101
//00000000 00000000 00000000 00000111
printf("%d\n", c); //7
return 0;
}
10.4 ^ 按位异或
相同为0,相异为1
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a ^ b;
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 00000101
//00000000 00000000 00000000 00000110
printf("%d\n", c); //6
return 0;
}
10.5 练习
不用临时变量交换a、b
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d,%d\n", a, b); //5,3
return 0;
}
求一个数在内存中的二进制1的个数
#include <stdio.h>
//方法二 效率低,循环32次
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
for (int i = 0; i < 32; i++) {
if (((num >> i) & 1) == 1)
count++;
}
printf("在内存中二进制1的个数为:%d\n", count);
return 0;
}
#include <stdio.h>
//方法三
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
while (num)
{
num &= (num - 1);
count++;
}
printf("在内存中二进制1的个数为:%d\n", count);
return 0;
}
数组名去掉就是数组的类型
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%zd\n", sizeof(arr)); //40
printf("%zd\n", sizeof(int[10])); //40
return 0;
}
11 C语言操作符详解(2)
11.1 sizeof
sizeof() 括号里面实际不会参加运算
#include <stdio.h>
int main()
{
short a = 0;
int b = 10;
printf("%zd\n", sizeof(a = b + 5)); //2 sizeof括号里面实际不会参加运算
printf("%d\n", a); //0 所以a还是原来的值
return 0;
}
11.2 逗号表达式
从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1); //0, a=2+10=12, 12, b=12+1=13
printf("%d\n", c); //13
return 0;
}
11.3 访问一个结构的成员
1.结构体.成员名
2.结构体指针->成员名
#include <stdio.h>
struct Student
{
char name[20];
int age;
char id[20];
};
int main()
{
struct Student s1 = { "张三",22,"20230510" };
printf("%s\n", s1.name); //张三
printf("%d\n", s1.age); //22
printf("%s\n", s1.id); //20230510
struct Student* ps = &s1;
printf("%s\n", ps->name);
printf("%d\n", ps->age);
printf("%s\n", ps->id);
printf("%s\n", (*ps).name);
printf("%d\n", (*ps).age);
printf("%s\n", (*ps).id);
return 0;
}
11.4 整型提升
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
#include <stdio.h>
int main()
{
char a = 3;
//00000000 00000000 00000000 00000011 - 3的整型二进制
//00000011 - a char是一个字节,所以被截断
char b = 127;
//00000000 00000000 00000000 01111111
//01111111 - b
//a+b a和b的值被提升为普通整形(按符号位提升,0补全0,1补全1),然后再执行加法运算
//00000000 00000000 00000000 00000011 - a
//00000000 00000000 00000000 01111111 - b
//00000000 00000000 00000000 10000010 - c
char c = a + b;
//10000010 - c
//11111111 11111111 11111111 10000010 - 补码 按符号位提升
//11111111 11111111 11111111 10000001 - 反码
//10000000 00000000 00000000 01111110 - 原码
printf("%d\n", c); //-126
return 0;
}
#include <stdio.h>
int main()
{
char a = 1;
printf("%zd\n", sizeof(a)); //1
printf("%zd\n", sizeof(+a)); //4 只要参加运算就会整型提升
printf("%zd\n", sizeof(-a)); //4
printf("%zd\n", sizeof(!a)); //1
return 0;
}
12 初识指针(1)
12.1 野指针
#include <stdio.h>
int main()
{
int* p; //未初始化
*p = 10;
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 12; i++) {
*(p++) = i; //指针越界
}
return 0;
}
#include <stdio.h>
int* test() {
int a = 10;
return &a;
}
int main()
{
int* p = test(); //a是局部变量,返回时已经释放
*p = 20;
printf("%d\n", *p);
return 0;
}
13 初识指针(2)
13.1 如何避免野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放即使置NULL
4.指针使用之前检查有效性
#include <stdio.h>
int main()
{
int* p = NULL;
int a = 10;
p = &a;
p = NULL;
if (p != NULL) { //检查有效性
*p = 20;
}
return 0;
}
13.2 指针运算
指针 - 指针 得到中间元素的个数
#include <stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%Id\n", &a[9] - &a[0]); //9
return 0;
}
13.3 练习
计算数组的长度
#include <stdio.h>
int sLen(char* arr) {
char* start = arr;
char* end = arr;
while (*end != '\0')
{
end++;
}
return end - start;
}
int main()
{
char arr[] = "bit";
int len = sLen(arr);
printf("%d\n", len);
return 0;
}
C语言标准规定不允许和数组的首地址的前一个地址进行比较
#include <stdio.h>
#define VALUES 5
int main()
{
int a[VALUES];
//第一种 推荐第一种
for (int* p = &a[VALUES]; p > &a[0];) {
*--p = 0;
}
//第二种
for (int* p = &a[VALUES - 1]; p >= &a[0]; p--) {
*p = 0;
}
return 0;
}
13.4 二级指针
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** pp = &p; //二级指针
//int*** ppp = &pp; //三级指针
//......
return 0;
}
13.5 指针数组
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
//整型数组 - 存放整型
//字符数组 - 存放字符
//指针数组 - 存放指针
int* arr[] = { &a,&b,&c };
return 0;
}
14 初识结构体(VS环境-C语言实用调试技巧(1))
14.1 定义结构体
#include <stdio.h>
//struct 结构体关键字 Student - 结构体标签 struct Student - 结构体类型
struct Student1
{
char name[20];
int age;
char id[20];
}s1, s2, s3; //s1, s2, s3 是三个全局的结构体变量
typedef struct Student2
{
char name[20];
int age;
char id[20];
}Student2; //定义类型别名
int main()
{
struct Student1 s1; //局部变量
Student2 s2;
return 0;
}
14.2 初始化结构体
#include <stdio.h>
typedef struct Student
{
char name[20];
int age;
char id[20];
}Student;
typedef struct Teacher
{
Student s;
char name[20];
int age;
}Teacher;
int main()
{
Teacher t = { {"张三",22,"20230510"},"李四",36 };
printf("%s\n", t.s.name); //张三
printf("%d\n", t.s.age); //22
printf("%s\n", t.s.id); //20230510
printf("%s\n", t.name); //李四
printf("%d\n", t.age); //36
return 0;
}
14.3 访问成员变量
结构体传参传结构体地址
#include <stdio.h>
//第一种 开辟额外的结构体内存空间
void print1(Teacher t) {
printf("%s\n", t.s.name);
printf("%d\n", t.s.age);
printf("%s\n", t.s.id);
printf("%s\n", t.name);
printf("%d\n", t.age);
}
//第二种 选第二种,不会开辟额外的结构体内存空间,只会开辟地址空间8个字节
void print2(Teacher* t) {
printf("%s\n", t->s.name);
printf("%d\n", t->s.age);
printf("%s\n", t->s.id);
printf("%s\n", t->name);
printf("%d\n", t->age);
}
int main()
{
Teacher t = { {"张三",22,"20230510"},"李四",36 };
print1(t);
print2(&t);
return 0;
}
15 数据的存储(1)
看 00:50:00
15.1 大、小端
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中
小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中
16 数据的存储(3)
16.1 char字符
char -128 ~ 127
signed char -128 ~ 127
usigned char 0 ~ 2^8-1
#include <stdio.h>
int main()
{
char a = 128;
char b = -128;
printf("%d\n", a); //-128 127+1 = 10000000 = -128
printf("%d\n", b); //-128
return 0;
}
17 数据的存储(4)(多看几遍)
18 指针详解(1)
国内程序员的知乎: https://segmentfault.com/
18.1 segmentfault异常
#include <stdio.h>
int main()
{
char* p = "abcdef"; //常量字符串,不能被修改
printf("%c\n", *p); //a
printf("%s\n", p); //abcdef
*p = 'W'; //会报segmentfault异常
printf("%s\n", p);
//正确写法
//const char* p = "abcdef";
return 0;
}
19 指针详解(2)
19.1 数组指针
#include <stdio.h>
int main()
{
int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };
int(*p1)[10] = &arr1;
char* arr2[5]; //指针数组
char* (*p2)[5] = &arr2; //数组指针
//char* p指向的数组的元素类型是char*
//* 说明p是指针
//p 指针变量的名字
//[5] p指向的数组是5个元素的
return 0;
}
19.2 数组指针的使用
#include <stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int(*p)[10] = &arr;
//第一种
for (int i = 0; i < 10; i++) {
printf("%d ", (*p)[i]);
}
printf("\n");
//第二种
for (int i = 0; i < 10; i++) {
printf("%d ", *(*p + i)); //*p == arr
}
printf("\n");
//第三种
//数组指针不是这么用,一维数组直接用第三种遍历就行,不用第一、第二种
//一般数组指针用在二维数组以上
int* pa = arr;
for (int i = 0; i < 10; i++) {
printf("%d ", *(pa + i));
}
return 0;
}
#include <stdio.h>
void printA(int(*p)[5], int row, int column) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
printf("%d ", *(*(p + i) + j));
//参照下面一维数组 *(p + i) == p[i]
//printf("%d ", *(p[i] + j));
//printf("%d ",p[i][j]);
//printf("%d ", (*(p + i))[j]);
}
printf("\n");
}
}
int main()
{
int a1[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
printA(a1, 3, 5); //a - 数组名 - 数组名就是首元素地址
把二维数组想成里面存放着多个一维数组
//a[3][5] - 有3个元素,分别是第一行、第二行、第三行
//二维数组的首元素地址是第一行的地址
int a2[10] = { 0,1,2,3,4,5,6,7,8,9 };
int i = 0;
int* p = a2;
for (int i = 0; i < 10; i++) {
printf("%d ", *(p + i));
//printf("%d ", *(a2 + i));
//printf("%d ", a2[i]);
//printf("%d ", p[i]); //*(p + i) == *(a2 + i) == a2[i] == p[i]
}
return 0;
}
看 00:47:00 理解数组指针和指针数组
20 指针详解(3)
20.1 函数指针
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
int main()
{
//函数名 和 &函数名 都是函数的地址
printf("%p\n", add);
printf("%p\n", &add);
//数组指针 - 是指向数组的指针
//函数指针 - 是指向函数的指针 - 存放函数地址的一个指针
int arr[10] = { 0 };
int(*p)[10] = &arr;
//参照 int(*p)[10]
int(*pa)(int, int) = &add; //函数指针类型 - int(*)(int,int)
// *pa 和 pa 一样,两种方法都可以
//pa好理解一点,看上一节数组指针的使用就能理解
printf("%d\n", pa(2, 3)); //5
printf("%d\n", (*pa)(2, 3)); //5
return 0;
}
21 指针详解(4)
21.1 函数指针类型
#include <stdio.h>
//void(*signal(int, void(*)(int)))(int)太复杂了
//对函数指针类型重命名,定义类型别名简化
typedef void(*pvt)(int);
int main()
{
//signal是一个函数,有两个参数,分别是int 整型,void(*)(int) 函数指针类型
//signal的返回值也是一个函数指针
void(*signal(int, void(*)(int)))(int);
pvt signal(int, pvt);
return 0;
}
21.2 函数指针的数组
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
int mul(int x, int y) {
return x * y;
}
int divide(int x, int y) {
return x / y;
}
typedef int(*ifii)(int, int);
int main()
{
int(*p[4])(int, int) = { add,sub,mul,divide }; //函数指针的数组
//ifii p[4] = { add,sub,mul,divide }; //感觉这样好理解一些
for (int i = 0; i < 4; i++) {
printf("%d\n", p[i](2, 3)); //5 -1 6 0
}
return 0;
}
22 指针详解(5)
22.1 回调函数
回调函数就是一个通过函数指针调用的函数。
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
int mul(int x, int y) {
return x * y;
}
int divide(int x, int y) {
return x / y;
}
void menu() {
printf("\n");
printf("1.加 2.减\n");
printf("3.乘 4.除\n");
printf("0.退出\n");
}
void callBack(int(*p)(int, int)) {
int a = 0, b = 0;
printf("请输入两个操作数:");
scanf("%d%d", &a, &b);
printf("%d\n", p(a, b)); //被调用的函数叫回调函数
}
int main()
{
int input = 0;
do {
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
callBack(add);
break;
case 2:
callBack(sub);
break;
case 3:
callBack(mul);
break;
case 4:
callBack(divide);
break;
case 0:
printf("退出\n");
break;
default:
printf("错误\n");
break;
}
} while (input);
return 0;
}
22.2 函数指针的数组的数组指针
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
int mul(int x, int y) {
return x * y;
}
int divide(int x, int y) {
return x / y;
}
typedef int(*ifii)(int, int);
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr; //数组指针
int(*p[4])(int, int) = { add,sub,mul,divide }; //函数指针的数组
int(*(*pp)[4])(int, int) = &p; //数组指针
//参考int(*p)[10]
//得int(* )(int, int) (*pp)[4]
//pp是一个数组指针,指针指向的数组有4个元素
//每个元素的类型是一个函数指针
return 0;
}
23 指针详解(6)
(6)—(7) 讲解qsort函数的使用,可以选择看
23.1 void* 指针类型
#include <stdio.h>
int main()
{
int a = 10;
void* p = &a; //void* 类型的指针 可以接收任意类型的地址
//*p = 0; //void* 类型的指针 不能进行解引用操作
//p++; //void* 类型的指针 不能进行 + - 整数操作
return 0;
}
24 自定义数据类型-结构体(1)
24.1 匿名结构体类型
//匿名结构体类型
struct {
char name[20];
int age;
}s; //变量不能省略
24.2 结构的自引用
//结构的自引用 链表
struct Node
{
int data;
struct Node* next; //要用指针
};
25 自定义数据类型-结构体(2)
25.1 结构体内存对齐
让占用空间小的成员尽量集中在一起
25.2 设置默认对齐数
#pragma pack() //取消设置的默认对齐数
#pragma pack(2) //设置默认对齐数为2
26 文件操作(1)
26.1 什么是文件
分为程序文件和数据文件
26.2 文件名
文件名包含3部分:文件路径+文件主干名+文件后缀
例如:c:\code\ test .txt
26.3 文件类型
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
26.4 文件缓冲区
ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的
26.5 文件指针
FILE* file = NULL;
27 文件操作(2)
27.1 文件打开
#include <stdio.h>
int main()
{
//打开文件test.txt
//相对路径
//.. 表示上一级路径
//. 表示当前路径
fopen("../../test1", "r");
fopen("test2.txt", "r");
//绝对路径
fopen("F:\\vsworkspace\\helloworld\\test3.txt", "r");
return 0;
}
打开方式如下:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 出错 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建议一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建立一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
27.2 写文件
#include <stdio.h>
int main()
{
//打开文件
FILE* fpWrite = fopen("test.txt", "w");
if (fpWrite == NULL) {
printf("%s\n", strerror(errno));
return 0;
}
//写文件
fputc('a', fpWrite);
//关闭文件
fclose(fpWrite);
fpWrite = NULL;
return 0;
}
27.3 读文件
#include <stdio.h>
int main()
{
//打开文件
FILE* fpRead = fopen("test.txt", "r");
if (fpRead == NULL) {
printf("%s\n", strerror(errno));
return 0;
}
//读文件
printf("%c\n", fgetc(fpRead));
//关闭文件
fclose(fpRead);
fpRead = NULL;
return 0;
}
28 文件操作(3)
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
28.1 写一行
#include <stdio.h>
int main()
{
//打开文件
FILE* fpWrite = fopen("test.txt", "w");
if (fpWrite == NULL) {
printf("%s\n", strerror(errno));
return 0;
}
//写文件
fputs("hello\n", fpWrite);
fputs("world", fpWrite);
//关闭文件
fclose(fpWrite);
fpWrite = NULL;
return 0;
}
28.2 读一行
#include <stdio.h>
int main()
{
char b[1024] = "";
//打开文件
FILE* fpRead = fopen("test.txt", "r");
if (fpRead == NULL) {
printf("%s\n", strerror(errno));
return 0;
}
//读文件
fgets(b, 1024, fpRead);
printf("%s", b);
fgets(b, 1024, fpRead);
printf("%s", b);
//关闭文件
fclose(fpRead);
fpRead = NULL;
return 0;
}
28.3 格式化写
#include <stdio.h>
struct S {
char name[10];
int age;
float score;
};
int main()
{
struct S s = { "lisi",22,98.8f };
FILE* fw = fopen("test.txt", "w");
if (fw == NULL) {
return 0;
}
fprintf(fw, "%s %d %.1f", s.name, s.age, s.score);
fclose(fw);
fw = NULL;
return 0;
}
28.4 格式化读
#include <stdio.h>
struct S {
char name[10];
int age;
float score;
};
int main()
{
struct S s = { 0 };
FILE* fr = fopen("test.txt", "r");
if (fr == NULL) {
return 0;
}
fscanf(fr, "%s %d %f", s.name, &(s.age), &(s.score)); //读文件内容写到构造函数
printf("%s %d %.1f\n", s.name, s.age, s.score);
fclose(fr);
fr = NULL;
return 0;
}
29 文件操作(4)
29.1 二进制写
#include <stdio.h>
struct S {
char name[10];
int age;
float score;
};
int main()
{
struct S s = { "lisi",22,98.8f };
FILE* pw = fopen("test.txt", "wb");
if (pw == NULL) {
return 0;
}
fwrite(&s, sizeof(struct S), 1, pw);
fclose(pw);
pw = NULL;
return 0;
}
29.2 二进制读
#include <stdio.h>
struct S {
char name[10];
int age;
float score;
};
int main()
{
struct S s = {0};
FILE* pr = fopen("test.txt", "rb");
if (pr == NULL) {
return 0;
}
fread(&s, sizeof(struct S), 1, pr);
printf("%s %d %.1f\n", s.name, s.age, s.score);
fclose(pr);
pr = NULL;
return 0;
}
30 文件操作(5)
30.1 文件的随机读写
fseek 根据文件指针的位置和偏移量来定位文件指针
ftell 返回文件指针相对于起始位置的偏移量
rewind 让文件指针的位置回到文件的起始位置
#include <stdio.h>
int main()
{
FILE* pr = fopen("test.txt", "r"); //文件内容:abcdef
if (pr == NULL) {
return 0;
}
fseek(pr, -2, SEEK_END); //向文件尾偏移 -2
printf("%d\n", ftell(pr)); //4 文件指针位置
fclose(pr);
pr = NULL;
return 0;
}
30.2 文件结束判定
feof 当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
1.文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)
例如:
fgetc 判断是否为EOF
fgets 判断返回值是否为NULL
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:fread 判断返回值是否小于实际要读的个数
31 C语言预处理(1)
在ANSIC的任何一种实现中,存在两个不同的环境。
第一种是翻译环境,在这个环境中源代码(.c文件)被转换为可执行的机器指令(.exe文件)。
第二种是执行环境,它用于实际执行代码。
31.1 翻译环境
分为编译和链接
源文件(.c文件)—> 目标文件(.obj文件)—> 可执行程序(.exe文件)
31.2 编译
编译分为预编译和编译和汇编
源文件(.c文件)-> 预编译(.i文件)-> 编译(.s文件)-> 目标文件(.obj文件)
31.2.1 预编译(预处理)
文本操作(.i文件内容):
1.#include 包含头文件
2.注释删除 使用空格替换注释
3.#define 真实值替换
31.2.2 编译
把C语言代码翻译成汇编代码
1.语法分析
2.词法分析
3.语义分析
4.符号汇总
31.2.3 汇编
把汇编代码转换成二进制指令
形成符号表
32 C语言预处理(2)
32.1 链接
1.合并段表
2.符号表的合并和符号表的重定位
32.2 运行环境
1.程序必须载入内存中。
2.程序的执行便开始。
3.开始执行程序代码。
4.终止程序。
32.3 预定义符号
#include <stdio.h>
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
printf("%d\n", __STDC_HOSTED__);
return 0;
}
32.4 预处理指令
32.4.1 #define 定义标识符
#include <stdio.h>
#define MAX 100
#define STR "hello"
#define I int
#define FOR for(;;) //也可以定义代码
int main()
{
I a = MAX;
printf("%d\n",a);
printf("%s\n", STR);
return 0;
}
33 C语言预处理(3)
33.1 预处理指令
33.1.1 #define 定义宏
注意:值是替换的,不是传参!!!
#include <stdio.h>
#define SQUARE(X) X*X
//定义宏建议使用括号
//#define SQUARE(X) ((X)*(X))
int main()
{
printf("%d\n", SQUARE(6)); //36
//SQUARE(5+1) == 5+1*5+1 == 11
printf("%d\n", SQUARE(5+1)); //11
return 0;
}
33.1.2 #
把一个宏参数变成对应的字符串
#include <stdio.h>
#define PRINT(X) printf(#X"=%d\n", X)
int main()
{
int a = 10;
int b = 20;
PRINT(a); //a=10
PRINT(b); //b=20
return 0;
}
33.1.3 ##
可以把位于它两边的符号合成一个符号
#include <stdio.h>
#define STRAND(X,Y) X##Y
int main()
{
int fValue = 10;
printf("%d\n", fValue); //10
printf("%d\n", STRAND(f,Value)); //10
//printf("%d\n", f##Value);
//printf("%d\n", fValue);
return 0;
}
34 C语言预处理(4)
宏定义不要传带有副作用(值会改变)的参数
#include <stdio.h>
#define MAX(X,Y) ((X)>(Y))?(X):(Y)
int main()
{
int a = 10;
int b = 11;
int c = a + 1; //a的值不会改变(没有副作用)
int max = MAX(a++, b++); //a和b的值会改变(副作用)
//int max = ((a++)>(b++))?(a++):(b++)
printf("%d\n", max); //12
printf("%d\n", a); //11
printf("%d\n", b); //13
return 0;
}
#undef 移除一个宏定义
35 C语言预处理(5)
35.1 条件编译
调式性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译
#include <stdio.h>
//#define DEBUG //需要打印就开启
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++) {
arr[i] = 0;
#ifdef DEBUG
printf("%d ", arr[i]);
#endif // DEBUG
}
return 0;
}
35.2 条件编译指令
#if 常量表达式
//...
#endif
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++) {
arr[i] = 0;
#if 1
printf("%d ", arr[i]); //常量表达式为真,执行
#endif
#if 0
printf("%d ", arr[i]); //常量表达式为假,不执行
#endif
}
return 0;
}
35.2.1 多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++) {
arr[i] = 0;
#if 1==1
printf("%d ", arr[i]); //执行
#elif 1==2
printf("%d ", arr[i]); //不执行
#else
printf("%d ", arr[i]); //不执行
#endif
}
return 0;
}
35.2.2 判断是否被定义
#if defined(symbol) == #ifdef symbol
#if !defined(symbol) == #ifndef symbol
#include <stdio.h>
#define DEBUG
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++) {
arr[i] = 0;
#if defined(DEBUG)
printf("%d ", arr[i]); //执行
#endif
#ifdef DEBUG
printf("%d ", arr[i]); //执行
#endif
#if !defined(DEBUG)
printf("%d ", arr[i]); //不执行
#endif
#ifndef DEBUG
printf("%d ", arr[i]); //不执行
#endif
}
return 0;
}
35.2.3 嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION1
msdos_version_option1();
#endif
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
#include <stdio.h>
#define DEBUG
#define A
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++) {
arr[i] = 0;
#if defined(DEBUG)
#ifdef A
printf("%d ", arr[i]); //执行
#endif
#ifdef B
printf("%d ", arr[i]); //不执行
#endif
#elif defined(HELLO)
#ifdef A
printf("%d ", arr[i]); //不执行
#endif
#ifdef B
printf("%d ", arr[i]); //不执行
#endif
#endif
}
return 0;
}
结语
最后本文章如果对您有一点点帮助的话,求赞 求收藏 求关注,您的支持是我创作的最大动力!
文章粗浅,希望对大家有帮助!