C语言总笔记
优先级最高的并不是真正意思上的运算符
优先级运算符名称或含义使用形式结合方向说明1[ ]数字下标数组名[常量表达式]左到右2( )圆括号(表达式)/函数名(形参表)左到右3.成员选择(对象)对象.成员名左到右4->成员选择(指针)对象指针->成员名单目运算符
优先级运算符名称或含义使用形式结合方向1-负号运算符-表达式右到左2(类型)强制类型转换(数据类型)表达式右到左3++自增运算符++变量名/变量名++右到左4- -自减运算符–变量名/变量名–右到左5*取值运算符*指针变量右到左6&取地址运算符&变量名右到左7!逻辑非运算符!表达式右到左8~按位取反运算符~表达式右到左9sizeof长度运算符sizeof(表达式)右到左双目运算符
优先级运算符名称或含义使用形式结合方向1/2*3%
优先级问题表达式经常误认为的结果实际结果.的优先级高于**p.fp所指对象的字段f对p取f偏移,作为指针,然后进行解除引用操作,*(p.f)()高于[ ]int (*ap)xxxxap是指向一个具有 n个int数组的指针[ ]高于 *int *ap[ ]ap是个指向int数组的指针,int(*ap)[ ]ap是个元素为int指针的数组 int *(ap[ ])函数( )高于*int *fp( )fp是个函数指针,所指函数返回int。int(*fp)( )fp是个函数,返回int *, int * (fp())==和!=高于位操作(val & mask !=0)(val & mask) != 0val & (mask!=0)==和!=高于赋值符c = getchar( ) != EOF(c = getchar())!=EOFc = (getchar != EOF)算术运算符高于移位运算符msb*(int*)b;//大于升序,小于反之 }qsort(nums,numsSize,sizeof(int),cmp);二维数组的快速排序
原题:输入:score = [,,], k = 2
输出:[,,]
解释:在上图中,S 表示学生,E 表示考试。
[*]下标为 1 的学生在第 2 场考试取得的分数为 11 ,这是考试的最高分,所以 TA 需要排在第一。
[*]下标为 0 的学生在第 2 场考试取得的分数为 9 ,这是考试的第二高分,所以 TA 需要排在第二。
[*]下标为 2 的学生在第 2 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第三。
void Quick_Sort(int *arr, int begin, int end)//快速排序,升序
{
if(begin > end)//只有一个数,基准确定,递归的出口
return;
int tmp = arr;
int i = begin;
int j = end;
while(i != j){
while(arr >= tmp && j > i)
j--;
while(arr <= tmp && j > i)
i++;
if(j > i){
int t = arr;
arr = arr;
arr = t;
}
}
arr = arr;
arr = tmp;
Quick_Sort(arr, begin, i-1);//递归,对基准数的左边进行相同操作
Quick_Sort(arr, i+1, end);//递归,对基准数的右边进行相同操作指针
双指针用法
注意:避免访问未初始化的指针
eg:
int cmp(const void*a,const void*b)
{
return *(int*)a>*(int*)b;//大于升序,小于反之
}
qsort(nums,numsSize,sizeof(int),cmp);数组的名字是数组第一个元素的地址
int sortCol;
int cmp(const void *a, const void *b){
return (*(int**)b) - (*(int**)a);
}
int** sortTheStudents(int** score, int scoreSize, int* scoreColSize, int k, int* returnSize, int** returnColumnSizes){
sortCol = k;
qsort(score,scoreSize,sizeof(int*),cmp);
*returnSize = scoreSize;
*returnColumnSizes = scoreColSize;
return score;
}用指针直接定义一个字符串,用下标逐个获取每个元素
int *a;//未初始化的指针
int *a=&b;//初始化的指针int a={1,2,3,4,5};
printf("%d %d %d %d %d
",*a,*(a+1),*(a+2),*(a+3),*(a+4));
//result:1 2 3 4 5数组指针和二维数组
char *str="i love you!";
int i,len;
len=strlen(str);
for(i=0;i<len;i++)
{
printf("%c",str);
//or printf("%c",*(str+i));
}
//redult: i love you!void指针和NULL指针
//void指针,通用指针
int a=1;
int b=2;
int c=3;
int d-4;
int e=5;
int p*={&a,&b,&c,&d,&e};
char *p2={"虽然说","人生","并没有","什么意义","但是爱情"};
printf("%s",p2);//p2指向字符串,*p2指向字符回溯算法
递归和回溯相辅相成
(纯暴力,不高效)
组合问题
切割问题
子集问题
排列问题
棋盘问题
抽象为树形结构
int temp={1,2,3,4,5};
int (*p)=&temp;//*p是一个指针,需要给指针一个地址,这里的数组指针指向的是整个数组,所以给整个数组的地址&temp,而不是temp(第一个数组元素的地址==数组名)
int i;
for(i=0;i<5;i++)
{
printf("%d",*(*p+i));//*p指向&temp,&temp+i指向每个元素的地址,再对其取值运算得到每个元素的值
}
//一个大地址(整个数组)里嵌套了五个小地址(五个数组元素)滑动窗口
(属于双指针)
哈希表
高效的散列,俗称哈希(基于快速存取的角度设计的,典型的空间换时间的做法)
通过把关键码值key(编号)映射到表中一个位置(数组的下标)来访问记录,以加快访问的速度。这个映射函数就叫做散列函数,存放记录的数组叫做散列表
键KEY组员的编号,如1,5,19……值VALUE组员的其他信息(包含姓名,年龄,战斗力等等)索引数组的下标0,1,2,3,4(用以快速定位和检索数据)哈希桶保存索引的数组(链表或者数组)数组成员为每一个索引值相同的多个元素哈希函数将文件编号映射到索引上,采用求余法。如:文件编号 191eg
int array={0};
//理解:array是二维数组名,是指向包含五个元素的数组的指针
//*(array+1) == array
//因为数组名是第一个元素的地址
//*(array+1) -> array -> &(array);
//**(array+1)=*(*(array+1)+0)-> array;
//*(*(array+2)+3) -> array;二分查找
有序升序数组查找target,没有则返回-1
*(array+i) == array;
*(*(array+i)+j) == array;
*(*(*(array+i)+j)+k)==array;Binary Search Tree:二叉搜索树(BST):降低搜索复杂度
特点:每一个根节点一定比左节点大,比右节点小
int array={{0,1,2},{3,4,5}};
int (*p)=array;
//此时p和array基本一致
*(array+i)==*(p+i) == array;
*(*(array+i)+j)==*(*(p+i)+j) == array;
*(*(*(array+i)+j)+k)== *(*(*(p+i)+j)+k)==array;链表
实例:链表的中间节点
int num=1;
int *p1=#
char *p2="你好";
void *p3;
p3=p2;
printf("%p
",p3);
p3=p1;
printf("%p",p3);void backtracking()
{
if(终止条件)
{
收集结果
return;
}
for(集合的元素集)//单层搜索逻辑
{
处理节点;
递归函数;
回溯操作;//撤销处理节点的情况
}
return;
}
int count;
int findTargetSumWays(int* nums, int numsSize, int target) {
count = 0;
backtrack(nums, numsSize, target, 0, 0);
return count;
}
//例子
void backtrack(int* nums, int numSize, int target, int index, int sum) {
if (index == numSize) {
if (sum == target) {
count++;
}
} else {
backtrack(nums, numSize, target, index + 1, sum + nums);
backtrack(nums, numSize, target, index + 1, sum - nums);
}
}实例:合并两个有序链表
#define DEFAULT_SIZE16//索引数组的大小
typedef struct _ListNode//定义一个链表
{
struct _ListNode *next;//链表指向下一个元素
int key;//键值
void *data;//数据value
}ListNode;
typedef ListNode *List;//当做一个链表用
typedef ListNode *Element;//当作一个元素(两者概念不一样,但实际时同一个东西)
typedef struct _HashTable
{
int TableSize;
List *Thelists; //不知道哈希桶有多少个,动态分配
}HashTable;
/*根据key计算索引,定位哈希桶的位置*/
int Hash(int key,int TableSize)
{
return (key%TableSize);//求余定位
}
//哈希表初始化
HashiTable *InitHash(int TableSize)
{
int i=0;
HashTable *hTable = NULL;
if(TableSize<=0)
{
TableSize=DEFAULT_SIZE;
}
hTable=(HashTable*)malloc(sizeof(HashTable));
if(NULL==hTable)
{
printf("HashTable malloc error.
");
return NULL;
}
hTable->TableSize=TableSize;
//为哈希桶分配内存空间,其为一个指针数组
hTable->Thelists=(List*)malloc(sizeof(List)*TableSize);
if(NULL=hTable->Thelists)
{
}
}int search(int* nums, int numsSize, int target){
int i=0,j=numsSize-1;
while(i<=j)
{
int temp=i+(j-i)/2;
if(nums==target)return temp;
else if(nums>target)
{
j=temp-1;
}
else if(nums<target)
{
i=temp+1;
}
}
return -1;
}二叉链表层序遍历
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;//节点存储的数据
struct node* left;//节点指向下一个左边的节点
struct node* right;//节点指向下一个右边的节点
}Node; //将 struct node 简写成 Node;
void preorder(Node* node)
{//先序遍历,先走根再走左边再走右边(根->左->右)
if(node!=NULL)
{
printf("%d
",node->data);//这里不能用node.data,因为这里node是一个指针,node->data相当于(*node).data;
preorder(node->left);//遍历node的左边
preorder(node->right);//遍历node的右边
}
}
void inorder(Node* node)
{//中序遍历,先走左边再走根再走右边(左->根->右)
if(node!=NULL)
{
inorder(node->left);//遍历node的左边
printf("%d
",node->data);//这里不能用node.data,因为这里node是一个指针,node->data相当于(*node).data;
inorder(node->right);//遍历node的右边
}
}
void postorder(Node* node)
{//后序遍历,先走左边再走右边再走根(左->右->根)
if(node!=NULL)
{
postorder(node->left);//遍历node的左边
postorder(node->right);//遍历node的右边
printf("%d
",node->data);//这里不能用node.data,因为这里node是一个指针,node->data相当于(*node).data;
}
}
int main()//主函数
{
Node n1,n2,n3,n4;//定义四个节点
n1.data=5;
n2.data=6;
n3.data=7;
n4.data=8;//赋值四个节点所存储的数据
n1.left=&n2;//n1的左下节点指向n2;
n1.right=&n3;//n1的右边连着n3;
n2.left=&n4;//n2的左边连着n4
n2.right=NULL;//安全点
n3.left=NULL;//安全点
n3.right=NULL;//安全点
n4.left=NULL;//安全点
n4.right=NULL;//安全点
preorder(&n1);//放根节点(不能直接放n1,n1是结构变量,应该放指针,也就是放该结构变量的地址)answer:5 6 8 7
inorder(&n1);//放根节点(不能直接放n1,n1是结构变量,应该放指针,也就是放该结构变量的地址)answer:8 6 5 7
postorder(&n1);//放根节点(不能直接放n1,n1是结构变量,应该放指针,也就是放该结构变量的地址)answer:8 6 7 5
}
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;//节点存储的数据
struct node* left;//节点指向下一个左边的节点
struct node* right;//节点指向下一个右边的节点
}Node; //将 struct node 简写成 Node;
typedef struct
{
Node *root;//要访问树的话只要访问到这棵树的根节点就行
}Tree;
void insert(Tree *tree,int value)//往一棵树里面插入一个数字value
{//将value包装成一个节点
Node *node=malloc(sizeof(Node));//动态分配,当这段子函数退出时,这个node不会被程序销毁掉
node->data=value//在该节点内存入value值
node->left=NULL//新节点,左右都没有东西
node->left=NULL//新节点,左右都没有东西
if(tree->root==NULL)//如果树本身就是空的话
{
tree->root=node;
}
else //如果树不是空的
{
Node *temp=tree->root;//定义一个临时节点等于树根和value作比较,
}
}位运算
位运算的由来
在计算机里面,任何数据最终都是用数字来表示的(不管是我们平时用的软件,看的图片,视频,还是文字)。
并且计算机运算单元只认识高低电位,转化成我们认识的逻辑,也就是 0 1 。
这就是导致计算机里面任何数据最终都是用二进制(0 1)来保存的数字。只是我们平时看到的图片、文字、软件都只从二进行数字转化而来的。
位运算符
!(C:\Users\86177\AppData\Roaming\Typora ypora-user-images\1679829103586.png)
常用位操作
判断奇偶
(x & 1) == 1 ---等价---> (x % 2 == 1)
(x & 1) == 0 ---等价---> (x % 2 == 0)
x / 2 ---等价---> x >> 1
x &= (x - 1) ------> 把x最低位的二进制1给去掉
x & -x -----> 得到最低位的1
x & ~x -----> 0
指定位置的位运算
将X最右边的n位清零:x & (~0 > n) & 1
获取x的第n位的幂值:x & (1 10,则执行回调函数。 { (tmp->pshow)(tmp->a); }} void show(int a){ printf("a的值是%d",a);} void main(){ TMP test; test.a = 11; test.pshow = show; func(&test);}终端显示:a的值是11 /*一般回调函数的用法为: 甲方进行结构体的定义(成员中包括回调函数的指针) 乙方定义结构体变量,并向甲方注册, 甲方收集N个乙方的注册形成结构体链表,在某个特定时刻遍历链表,进行回调。 当函数指针做为函数的参数,传递给一个被调用函数, 被调用函数就可以通过这个指针调用外部的函数,这就形成了回调一般的程序中回调函数作用不是非常明显,可以不使用这种形式最主要的用途就是当函数不处在同一个文件当中,比如动态库,要调用其他程序中的函数就只有采用回调的形式,通过函数指针参数将外部函数地址传入来实现调用函数的代码作了修改,也不必改动库的代码,就可以正常实现调用便于程序的维护和升级输入:head =
输出:
解释:链表只有一个中间结点,值为 3 。结构体指针---函数指针的封装
//抽象化统一接口语言typedef struct DEVICE_OP_ST//结构体里面定义的是这个对象/变量 可能包含的方法-->抽象出来{//简单理解成抽象方法 void(*open)(void * args);//打开外设 void(*close)();//关闭外设 void(*write)(void* args);//写入外设 void(*read)(void* args);//读取外设}device_op_st;//举例说明---这个结构体串口可以使用,SPI也可以使用,I2C也可以使用//虽然打开串口、打开SPI、打开I2C都不一样,但是都可以抽象成open,在这个基础上,把函数抽象出来////实例化抽象方法static device_op_st devices[]={ //把我们重写的函数通过函数指针的方式赋值进去 //定义一个UART设备 { .open=uart_open,//比如说如果是串口的open方法指向的是串口打开 .close=uart_close,//close-> uart_close .write=uart_write,//指针指向的位置是后面那个的函数入口 .read=uart_read, }, //定义一个SPI设备 { .open=spi_open, .close=spi_close, .write=spi_write, .read=spi_read, }, //定义非标准设备 //……}//具体重写串口操作函数void uart_open(void* args){ printf("打开串口");}void uart_close(){ printf("关闭串口");}void uart_write(void* args){ printf("写入串口");}void uart_read(void* args){ printf("读取串口");}//具体重写SPI操作函数void spi_open(void* args){ printf("打开SPI");}void spi_close(){ printf("关闭SPI");}void spi_write(void* args){ printf("写入SPI");}void spi_read(void* args){ printf("读取SPI");} //假设要读取所有的外设的数据到缓冲区void read_device_revdto_buff(){ int size= sizeof(devices)/sizeof(devices); char buff; for (int i=0;i
页:
[1]