C語言總筆記
優先級最高的并不是真正意思上的運算符
| 優先級 | 運算符 | 名稱或含義 | 使用形式 | 結合方向 | 說明 |
| 1 | [ ] | 數字下標 | 數組名[常量表達式] | 左到右 | |
| 2 | ( ) | 圓括號 | (表達式)/函數名(形參表) | 左到右 | |
| 3 | . | 成員選擇(對象) | 對象.成員名 | 左到右 | |
| 4 | -> | 成員選擇(指針) | 對象指針->成員名 |
單目運算符
| 優先級 | 運算符 | 名稱或含義 | 使用形式 | 結合方向 |
|---|---|---|---|---|
| 1 | - | 負號運算符 | -表達式 | 右到左 |
| 2 | (類型) | 強制類型轉換 | (數據類型)表達式 | 右到左 |
| 3 | ++ | 自增運算符 | ++變量名/變量名++ | 右到左 |
| 4 | - - | 自減運算符 | –變量名/變量名– | 右到左 |
| 5 | * | 取值運算符 | *指針變量 | 右到左 |
| 6 | & | 取地址運算符 | &變量名 | 右到左 |
| 7 | ! | 邏輯非運算符 | !表達式 | 右到左 |
| 8 | ~ | 按位取反運算符 | ~表達式 | 右到左 |
| 9 | sizeof | 長度運算符 | sizeof(表達式) | 右到左 |
雙目運算符
| 優先級 | 運算符 | 名稱或含義 | 使用形式 | 結合方向 |
|---|---|---|---|---|
| 1 | / | |||
| 2 | * | |||
| 3 | % |
?
| 優先級問題 | 表達式 | 經常誤認為的結果 | 實際結果 |
|---|---|---|---|
| .的優先級高于* | *p.f | p所指對象的字段f | 對p取f偏移,作為指針,然后進行解除引用操作,*(p.f) |
| ()高于[ ] | int (*ap)[n] | xxxx | ap是指向一個具有 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) != 0 | val & (mask!=0) |
| ==和!=高于賦值符 | c = getchar( ) != EOF | (c = getchar())!=EOF | c = (getchar != EOF) |
| 算術運算符高于移位運算符 | msb<< 4 + lsb | (mab<<4) +lsb | msb<<(4+lsb) |
排序
快速排序
void Quick_Sort(int *arr, int begin, int end)//快速排序,升序
{
if(begin > end)//只有一個數,基準確定,遞歸的出口
return;
int tmp = arr[begin];
int i = begin;
int j = end;
while(i != j){
while(arr[j] >= tmp && j > i)
j--;
while(arr[i] <= tmp && j > i)
i++;
if(j > i){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
arr[begin] = arr[i];
arr[i] = tmp;
Quick_Sort(arr, begin, i-1);//遞歸,對基準數的左邊進行相同操作
Quick_Sort(arr, i+1, end);//遞歸,對基準數的右邊進行相同操作
int cmp(const void*a,const void*b)
{
return *(int*)a>*(int*)b;//大于升序,小于反之
}
qsort(nums,numsSize,sizeof(int),cmp);
二維數組的快速排序
原題:輸入:score = [[10,6,9,1],[7,5,11,2],[4,8,3,15]], k = 2
輸出:[[7,5,11,2],[10,6,9,1],[4,8,3,15]]
解釋:在上圖中,S 表示學生,E 表示考試。
- 下標為 1 的學生在第 2 場考試取得的分數為 11 ,這是考試的最高分,所以 TA 需要排在第一。
- 下標為 0 的學生在第 2 場考試取得的分數為 9 ,這是考試的第二高分,所以 TA 需要排在第二。
- 下標為 2 的學生在第 2 場考試取得的分數為 3 ,這是考試的最低分,所以 TA 需要排在第三。
int sortCol;
int cmp(const void *a, const void *b){
return (*(int**)b)[sortCol] - (*(int**)a)[sortCol];
}
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;
}
指針
雙指針用法
注意:避免訪問未初始化的指針
eg:
int *a;//未初始化的指針
int *a=&b;//初始化的指針
數組的名字是數組第一個元素的地址
int a[5]={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[i]);
//or printf("%c",*(str+i));
}
//redult: i love you!
數組和指針的區別
數組名只是一個地址,不可修改。而指針變量是一個左值,是可修改的
指針數組
存放指針變量的數組
int a=1;
int b=2;
int c=3;
int d-4;
int e=5;
int p*[5]={&a,&b,&c,&d,&e};
char *p2[5]={"雖然說","人生","并沒有","什么意義","但是愛情"};
printf("%s",p2[3]);//p2[3]指向字符串,*p2[3]指向字符
數組指針
//指向整個數組的指針
//之前的指針指向的是數組某個元素的地址
int temp[5]={1,2,3,4,5};
int (*p)[5]=&temp;//*p是一個指針,需要給指針一個地址,這里的數組指針指向的是整個數組,所以給整個數組的地址&temp,而不是temp(第一個數組元素的地址==數組名)
int i;
for(i=0;i<5;i++)
{
printf("%d",*(*p+i));//*p指向&temp,&temp+i指向每個元素的地址,再對其取值運算得到每個元素的值
}
//一個大地址(整個數組)里嵌套了五個小地址(五個數組元素)
二維數組和指針
int array[5][5]={0};
//理解:array是二維數組名,是指向包含五個元素的數組的指針
//*(array+1) == array[1]
//因為數組名是第一個元素的地址
//*(array+1) -> array[1] -> &(array[1][0]);
//**(array+1)=*(*(array+1)+0)-> array[1][0];
//*(*(array+2)+3) -> array[2][3];
*(array+i) == array[i];
*(*(array+i)+j) == array[i][j];
*(*(*(array+i)+j)+k)==array[i][j][k];
數組指針和二維數組
int array[2][3]={{0,1,2},{3,4,5}};
int (*p)[3]=array;
//此時p和array基本一致
*(array+i)==*(p+i) == array[i];
*(*(array+i)+j)==*(*(p+i)+j) == array[i][j];
*(*(*(array+i)+j)+k)== *(*(*(p+i)+j)+k)==array[i][j][k];
void指針和NULL指針
//void指針,通用指針
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[index]);
backtrack(nums, numSize, target, index + 1, sum - nums[index]);
}
}
滑動窗口

(屬于雙指針)
哈希表
高效的散列,俗稱哈希(基于快速存取的角度設計的,典型的空間換時間的做法)
通過把關鍵碼值key(編號)映射到表中一個位置(數組的下標)來訪問記錄,以加快訪問的速度。這個映射函數就叫做散列函數,存放記錄的數組叫做散列表
| 鍵KEY | 組員的編號,如1,5,19…… |
|---|---|
| 值VALUE | 組員的其他信息(包含姓名,年齡,戰斗力等等) |
| 索引 | 數組的下標0,1,2,3,4(用以快速定位和檢索數據) |
| 哈希桶 | 保存索引的數組(鏈表或者數組)數組成員為每一個索引值相同的多個元素 |
| 哈希函數 | 將文件編號映射到索引上,采用求余法。如:文件編號 19 |
1eg
#define DEFAULT_SIZE 16//索引數組的大小
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)
{
}
}
二分查找
有序升序數組查找target,沒有則返回-1
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[temp]==target)return temp;
else if(nums[temp]>target)
{
j=temp-1;
}
else if(nums[temp]<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
}
Binary Search Tree:二叉搜索樹(BST):降低搜索復雜度
特點:每一個根節點一定比左節點大,比右節點小
#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作比較,
}
}
鏈表
實例:鏈表的中間節點
輸入:head = [1,2,3,4,5]
輸出:[3,4,5]
解釋:鏈表只有一個中間結點,值為 3 。
struct ListNode* middleNode(struct ListNode* head){
struct ListNode*p=head,*q=head;
while(p!=NULL&&p->next!=NULL)
{
p=p->next->next;
q=q->next;
}
return q;
}
實例:合并兩個有序鏈表
輸入:l1 = [1,2,4], l2 = [1,3,4]
輸出:[1,1,2,3,4,4]
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode* list3=(struct ListNode*)malloc(sizeof(struct ListNode));
list3->next=NULL;
struct ListNode*p=list3,*head=list3;
while(list1!=NULL&&list2!=NULL)
{
if(list1->val<=list2->val)
{
p->next=list1;
list1=list1->next;
p=p->next;
p->next=NULL;
}
else
{
p->next=list2;
list2=list2->next;
p=p->next;
p->next=NULL;
}
}
while(list1==NULL&&list2!=NULL)
{
p->next=list2;
list2=list2->next;
p=p->next;
p->next=NULL;
}
while(list1!=NULL&&list2==NULL)
{
p->next=list1;
list1=list1->next;
p=p->next;
p->next=NULL;
}
return head->next;
}
二叉鏈表層序遍歷
/*題目:
給你一棵二叉樹的根節點 root 和一個正整數 k 。
樹中的 層和 是指 同一層 上節點值的總和。
返回樹中第 k 大的層和(不一定不同)。如果樹少于 k 層,則返回 -1 。
注意,如果兩個節點與根節點的距離相同,則認為它們在同一層。*/

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
int cmp(const void* a,const void* b)
{
//return *(long long int *)b-*(long long int *)a;//不寫這條
long long x = *(long long *)b;
long long y = *(long long *)a;
if (x == y) {
return 0;
} else if (x > y) {
return 1;
}
return -1;
}
long long kthLargestLevelSum(struct TreeNode* root, int k)
{
long long int sz[100000]={0};//計算每一層數值
struct TreeNode* t[2][40000];
//定義一個二維數組存放每一層的指針,
//我的想法是不把樹存成線性的,而是分層存放
//由于有的樣例層數過多所以要哈希一次
for(int i=0;i<2;i++)
{
for(int j=0;j<40000;j++)
{
t[i][j]=NULL;//初始化
}
}
t[0][0]=root;//第一個放進去
if(root==NULL) return 0;
long long int ceng=0;//計算多少層
long long int next=0;//用于存放下一層
long long int l=0;//用于遍歷當前層
while(t[ceng%2][0]!=NULL)//循環條件是當前層得有東西
{
if(t[ceng%2][l]==NULL)//當當前層走到結尾時,要換層
{
t[ceng%2][0]=NULL;//給當前用于判斷是否要循環的頭指針給NULL了
ceng++;
next=0;//初始化
l=0;
}
else
{
//有東西就放到下一層
if(t[ceng%2][l]->left!=NULL)
t[(ceng+1)%2][next++]=t[(ceng)%2][l]->left;
if(t[ceng%2][l]->right!=NULL)
t[(ceng+1)%2][next++]=t[(ceng)%2][l]->right;
//同時計算這一層的數值
sz[ceng]+=t[ceng%2][l]->val;
if(l>0) t[ceng%2][l]=NULL;
//給搜索過的地方置空,但保留判斷循環的頭部指針
l++;
}
}
qsort(sz,ceng,sizeof(sz[0]),cmp);//從小到大排列
if(k>ceng) return -1;
return sz[k-1];
}
位運算
位運算的由來
在計算機里面,任何數據最終都是用數字來表示的(不管是我們平時用的軟件,看的圖片,視頻,還是文字)。
并且計算機運算單元只認識高低電位,轉化成我們認識的邏輯,也就是 0 1 。
這就是導致計算機里面任何數據最終都是用二進制(0 1)來保存的數字。只是我們平時看到的圖片、文字、軟件都只從二進行數字轉化而來的。
位運算符


常用位操作
判斷奇偶
(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)
獲取x的第n位值:(x >> n) & 1
獲取x的第n位的冪值:x & (1 << n)
僅將第n位置為1:x | (1 << n)
僅將第n位置為0:x & (~(1 << n))
將x最高位至第n位(含)清零:x & ((1 << n) - 1)
將第n位至第0位(含)清零:x & (~((1 << (n + 1)) - 1))
異或結合律
x ^ 0 = x, x ^ x = 0
x ^ (~0) = ~x, x ^ (~x) = ~0
a ^ b = c, a ^ c = b, b ^ c = a
(有沒有點乘法結合律的意思)
字母表示:(a ^ b) ^ c = a ^ (b ^ c)
圖形表示:(☆ ^ ◇) ^ △ = ☆ ^ (◇ ^ △)
UINT32_C
UINT32_C是一個宏,它定義了類型uint_least32_t的整數常數.
c - 1 << 31不能用 'int'類型表示嗎?
在此代碼上
uint32_t z;
z = 1 << 31;
最佳答案
使1無符號:
uint32_t z;
z = UINT32_C(1) << 31;
異或交換
int x=10,y=7;
x=x^y;
y=x^y;
x=x^y;
//此時x=7,y=10;
這個函數功能:返回輸入數據中,二進制中‘1’的個數。
對于不同的使用類型,可以采用采用以下函數:
__builtin_popcount = int
__builtin_popcountl = long int
__builtin_popcountll = long long
1 __builtin_ctz( ) / __buitlin_ctzll( )
用法:返回括號內數的二進制表示形式中末尾0的個數。
int x=__builtin_ctz(64);
//64-->1000000
//x=6;
2 __builtin_clz( ) / __builtin_clzll( )
注:ll指long long,是64位
用法:返回括號內數的二進制表示形式中前導0的個數。
int x=__builtin_clz(63);
//63-->0000 0000 0000 0000 0000 0000 0011 1111;
//x=26;
3 __builtin_popcount( )
用法:返回括號內數的二進制表示形式中1的個數。
int x= __builtin_popcount(4095);
//4095-->1111 1111 1111;
//x=12;
4 __builtin_parity( )
用法:返回括號內數的二進制表示形式中1的個數的奇偶性(偶:0,奇:1)。
int x=__builtin_parity(1);
//x=1;
//輸出:1
//1里面有1個1,所以當然輸出1咯。
5 __builtin_ffs( )
用法:返回括號內數的二進制表示形式中最后一個1在第幾位(從后往前)。
int x=__builtin_ffs(84);
//x=3;
//輸出:3
//84=(101100),所以是3。
6 __builtin_sqrt( )
用法:快速開平方。(8位)
7 __builtin_sqrtf( )
用法:快速開平方。(4位)
來自陜西
C語言刷題常用基礎知識
一、數組
-
數組的申請
a. 一維數組的申請
int* num = (int*)malloc(sizeof(int) * 10);b. 二維數組的申請
int** num = (int**)malloc(sizeof(int*) * 10); for (int i = 0; i < 10; i++) { num[i] = (int*)malloc(sizeof(int) * 10); }c. 數組都賦0值
對于malloc申請的數組要這樣:
int* num = (int*)malloc(sizeof(int) * 10); memset(num, 0, sizeof(int) * 10);對于非malloc申請的數組要這樣:
int num[10]; memset(num, 0, sizeof(num)); -
比較函數(入參為malloc申請的數組)
a. 一維數組
int cmp(const void* a, const void* b) { int* aa = (int*)a; int* bb = (int*)b; //return *aa - *bb; //從小到大排序 return *bb - *aa; //從大到小排序 }b. 二維數組
//假設數組是2列 int cmp(const void* a, const void* b) { int* aa = *(int**)a; int* bb = *(int**)b; //0列相同時,比較1列 if (aa[0] == bb[0]) { //return aa[1] - bb[1]; //從小到大排序 return bb[1] - aa[1]; //從大到小排序 } //0列不相同時 //return aa[1] - bb[1]; //從小到大排序 return bb[1] - aa[1]; //從大到小排序 }比較函數有了后,就可以進行排序,用qsort
//比如對 int* nums 進行排序,數組大小為10,比較函數為cmp //第一個參數為數組,第二個參數為數組大小,第三個參數為數組里單個元素的大小,第四個參數為比較函數cmp qsort(nums, 10, sizeof(int), cmp); -
力扣實現函數中
int* returnSize
及
int** returnColumnSize
的說明
int* returnSize 用來存二維數組的行的大小,用指針是方面時實修改大小,比如有10行,就這樣寫:
*returnSize = 10;int** returnColumnSize 用來存二維數組列的大小,這里是用二維指針存,第一維用來指向這個數組,第二維用來指向每列的大小,給returnColumnSize賦值的時候要先申請空間,比如有size列,每列大小都為10,就這樣寫:
*returnColumnSize = (int*)malloc(sizeof(int) * size); for (int i = 0; i < size; i++) { (*returnColumnSize)[i] = 10; }
int** myMalloc(int r, int c, int* returnSize, int** returnColumnSizes){
int** ret = (int**)malloc(sizeof(int*) * r);
*returnColumnSizes = (int*)malloc(sizeof(int) * r);
*returnSize = r;
for(int i = 0; i < r; i++){
ret[i] = (int*)malloc(sizeof(int) * c);
(*returnColumnSizes)[i] = c;
}
return ret;
}
{
int i,j;
int r = imageSize;
int c = imageColSize[0];
int** ret = myMalloc(r,c,returnSize,returnColumnSizes);
}
二、字符串
常用字符串函數有如下這些:
strstr(arr, tmp); //在arr中從左到右查找第一個tmp,找到返回tmp開頭的字符串的指針
strchr(arr, ch); //在arr判斷是否有ch這個字符,沒有的話返回NULL,有的話返回非空
strcmp(arr1, arr2); //比較arr1和arr2的大小,arr1大返回正值,arr1小返回負值,arr1和arr2相等返回0
strcpy(arr, tmp, tmp_size); //把tmp字符串復制到arr字符串里
strcat(arr, tmp); //將tmp字符串拼接到arr字符串的末尾
strtok(arr, tmp); //按tmp分割字符串arr,返回第一個分割的部分
這里把完整的分割字符串過程寫一下:
//strtok會使原字符串arr會變化成去掉第一個分割后的剩余字符串,所以分割前請先復制原串
//這里假設被分割成row個,每個的長度最長為col,用out來存每個分割部分
char** out = (char**)malloc(sizeof(char*) * row);
int idx = 0;
char* p = strtok(arr, tmp);
while (p != NULL) {
out[idx] = (char*)malloc(sizeof(char) * (col + 1)); //加1是字符串有結尾字符'/0'
out[idx++] = p;
p = strtok(NULL, tmp); //這里要用NULL
}
三、指針
指針是C語言中比較常用的類型,也是C語言的靈魂,也是難點,出錯點。主要還是知道到底想要表達什么意思也就自然而然了。指針是地址的意思,不同類型的變量所需地址空間大小不一樣,所以也就需要區分不同類型的指針。“*”表示指針,“&”表示取地址。在判斷指針是指向什么類型的時候,可以把變量去掉,剩下的就是指針所指向的。
int* a; //指向整型的指針,指向 int*
int** a; //指向整型指針的指針,指向 int**
int* a[n]; //指針數組,每個數組成員都是一個整型指針,int*,總共有n個指針
(int*)a[n]; //數組指針,指向數組的指針,指向(int*)[],總共就1個指針
int* func(); //指針函數,函數的返回值是1個指針
(int*)func(); //函數指針,指向函數的指針,指向(int*)(),總共就1個指針
說明:指針類型的變量的值是隨時變化的,相當于全局變量,所以在函數傳參數的時候,如果要實時保留值的變化,就傳入指針類型變量或者全局變量。
與指針相關的數據結構,如列表、樹等,在后面算法總結中再書寫。
C語言快速比較兩數大小——fmax,fmin函數
分配函數
malloc
-
數組的申請
a. 一維數組的申請
int* num = (int*)malloc(sizeof(int) * 10);b. 二維數組的申請
int** num = (int**)malloc(sizeof(int*) * 10); for (int i = 0; i < 10; i++) { num[i] = (int*)malloc(sizeof(int) * 10); }c. 數組都賦0值
對于malloc申請的數組要這樣:
int* num = (int*)malloc(sizeof(int) * 10); memset(num, 0, sizeof(int) * 10);對于非malloc申請的數組要這樣:
int num[10]; memset(num, 0, sizeof(num)); -
比較函數(入參為malloc申請的數組)
a. 一維數組
int cmp(const void* a, const void* b) { int* aa = (int*)a; int* bb = (int*)b; //return *aa - *bb; //從小到大排序 return *bb - *aa; //從大到小排序 }b. 二維數組
//假設數組是2列 int cmp(const void* a, const void* b) { int* aa = *(int**)a; int* bb = *(int**)b; //0列相同時,比較1列 if (aa[0] == bb[0]) { //return aa[1] - bb[1]; //從小到大排序 return bb[1] - aa[1]; //從大到小排序 } //0列不相同時 //return aa[1] - bb[1]; //從小到大排序 return bb[1] - aa[1]; //從大到小排序 }比較函數有了后,就可以進行排序,用qsort
//比如對 int* nums 進行排序,數組大小為10,比較函數為cmp //第一個參數為數組,第二個參數為數組大小,第三個參數為數組里單個元素的大小,第四個參數為比較函數cmp qsort(nums, 10, sizeof(int), cmp); -
力扣實現函數中
int* returnSize及int** returnColumnSize 的說明
int* returnSize 用來存二維數組的行的大小,用指針是方面時實修改大小,比如有10行,就這樣寫:
*returnSize = 10;int** returnColumnSize 用來存二維數組列的大小,這里是用二維指針存,第一維用來指向這個數組,第二維用來指向每列的大小,給returnColumnSize賦值的時候要先申請空間,比如有size列,每列大小都為10,就這樣寫:
*returnColumnSize = (int*)malloc(sizeof(int) * size); for (int i = 0; i < size; i++) { (*returnColumnSize)[i] = 10; }
realloc
C 庫函數 void *realloc(void *ptr, size_t size) 嘗試重新調整之前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小。
- ptr -- 指針指向一個要重新分配內存的內存塊,該內存塊之前是通過調用 malloc、calloc 或 realloc 進行分配內存的。如果為空指針,則會分配一個新的內存塊,且函數返回一個指向它的指針。
- size -- 內存塊的新的大小,以字節為單位。如果大小為 0,且 ptr 指向一個已存在的內存塊,則 ptr 所指向的內存塊會被釋放,并返回一個空指針。
該函數返回一個指針 ,指向重新分配大小的內存。如果請求失敗,則返回 NULL。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *str;
/* 最初的內存分配 */
str = (char *) malloc(15);
strcpy(str, "runoob");
printf("String = %s, Address = %p
", str, str);
/* 重新分配內存 */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %p
", str, str);
free(str);
return(0);
}
//String = runoob, Address = 0x7fa2f8c02b10
//String = runoob.com, Address = 0x7fa2f8c02b10
typedef的4種用法
1) 為基本數據類型定義新的類型名
typedef unsigned int COUNT;
2) 為自定義數據類型(結構體、共用體和枚舉類型)定義簡潔的類型名稱
typedef struct tagPoint
{
double x;
double y;
double z;
} Point;
3) 為數組定義簡潔的類型名稱
typedef int INT_ARRAY_100[100];
INT_ARRAY_100 arr;
4) 為指針定義簡潔的名稱
typedef char* PCHAR;
PCHAR pa;
枚舉
enum(枚舉)
枚舉是 C 語言中的一種基本數據類型,用于定義一組具有離散值的常量。,它可以讓數據更簡潔,更易讀。
枚舉類型通常用于為程序中的一組相關的常量取名字,以便于程序的可讀性和維護性。
定義一個枚舉類型,需要使用 enum 關鍵字,后面跟著枚舉類型的名稱,以及用大括號 {} 括起來的一組枚舉常量。每個枚舉常量可以用一個標識符來表示,也可以為它們指定一個整數值,如果沒有指定,那么默認從 0 開始遞增。
枚舉語法定義格式為:
enum 枚舉名 {枚舉元素1,枚舉元素2,……};
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
函數指針和結構體函數指針
函數指針
指針指向的是函數的入口地址
void callback_c()//被回調的函數
{
printf("callback to c
");
}
void callback_d()//被回調的函數
{
printf("callback to d
");
}
void call_b(void (*cb)())//參數是函數指針,指向函數的入口地址
{
//1.做其他……事情
//2.調用回調函數
if(cb!=NULL)cb();//函數指針
}
//以上簡單,以下復雜
//和回調函數最大的區別是后面是否攜帶參數
void not_fun_pointer)(void *arg)//空類型指針——不確定類型指針
{ //可以賦值任意變量類型的指針 int* arg,float* arg……
//傳遞進來的*不做要求,需要在函數里面對它的類型大小等做判斷,不然就會出錯
//假設當作int處理
int a;
if(arg==NULL)return;//判斷空
a=*(int*)arg;//首先進行強制類型轉換,(int*)強制說明指針指向的類型是int類型,*說明這是指針指向地址的內容
printf("not_fun_pointer:%d
",a);
int main(void)
{
int x=1;
float y=2.0f;
char ch='?';
not_fun_pointer(&y);//傳遞進去是float *,強制轉換出錯->打印出錯
not_fun_pointer(&x);//傳遞進去是int * ,打印出來是1
while((ch==getchar())!='q')//按q退出
{
if(ch=='c')
{
call_b(callback_c);
}
else if(ch=='d')
{
call_b(callback_d);
}
}
return 0;
}
}
函數指針的定義形式
returnType (*pointerName)(param list);
示例代碼
#include <stdio.h>
//返回兩個數中較大的一個
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int x, y, maxval;
//定義函數指針
int (*pmax)(int, int) = max; //也可以寫作int (*pmax)(int a, int b) = max
printf("Input two numbers:");
scanf_s("%d %d", &x, &y);
maxval = (*pmax)(x, y);
printf("Max value: %d
", maxval);
return 0;
}
結構體中定義函數指針
c語言中,如何在結構體中實現函數的功能?把結構體做成和類相似,讓他的內部有屬性,也有方法,
這樣的結構體一般稱為協議類,提供參考:
struct {
int funcid;
char *funcname;
int (*funcint)(); /* 函數指針 int 類型*/
void (*funcvoid)(); /* 函數指針 void類型*/
};
每次都需要初始化,比較麻煩
示例代碼
#include <stdio.h>
typedef struct
{
int a;
void (*pshow)(int);
}TMP;
void func(TMP *tmp)
{
if(tmp->a >10)//如果a>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個乙方的注冊形成結構體鏈表,在某個特定時刻遍歷鏈表,進行回調。
當函數指針做為函數的參數,傳遞給一個被調用函數,
被調用函數就可以通過這個指針調用外部的函數,這就形成了回調
一般的程序中回調函數作用不是非常明顯,可以不使用這種形式
最主要的用途就是當函數不處在同一個文件當中,比如動態庫,要調用其他程序中的函數就只有采用回調的形式,通過函數指針參數將外部函數地址傳入來實現調用
函數的代碼作了修改,也不必改動庫的代碼,就可以正常實現調用便于程序的維護和升級
#pragma warning(disable : 4996)
#include <stdio.h>
//#include <string.h>
void fun0() {
printf("%s
", __FUNCTION__);
}
void fun1() {
printf("%s
", __FUNCTION__);
}
void fun2() {
printf("%s
", __FUNCTION__);
}
int main()
{
typedef void (*pmax)();
pmax p1 = NULL;
int i = 2;
switch (i)
{
case 0:
p1 = fun0;
case 1:
p1 = fun1;
case 2:
p1 = fun2;
default:
break;
}
p1();
return(0);
}
結構體指針---函數指針的封裝
//抽象化統一接口語言
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[0]);
char buff[4096];
for (int i=0;i<size;i++)
{
//上層不需要知道具體做了什么
//隱藏具體外設的敏感信息,達到很好的擴展性
devices[i].open(NULL);
devices[i].read(buff);
devices[i].write(buff);
devices[i].close();
}
}
int main(void)
{
reead_device_revdto_buff();//有良好的抽象性和擴展性
return 0;
}
函數指針舉例說明----打印機

舉例


傳入子函數的參數是母體原參數的復制體
子函數對復制體進行操作是影響不到母體的
如果要對母體進行操作,用指針直接對地址直接進行操作
函數指針是指向函數的指針變量
回調函數是函數指針最常見的用途

函數指針的基本概念:
函數名可以被看做一個常量,保存了函數的內存地址(函數的內存地址存儲了函數開始執行的位置)
通過使用指針來保存函數的地址,可以創建指向函數的指針
函數指針使得我們能夠靈活的調用具有相同形式參數和返回值功能不同的函數,增加代碼的靈活性








函數指針也可以作為函數參數傳遞給其他函數
這樣函數內部就可以根據函數指針所指向的不同函數來調用不同的功能


面向對象的c編程--狗哥嵌入式
閑聊:高內聚低耦合
第一重:
內聚:把邏輯封裝到模塊里面
高內聚:自己的事情自己做(模塊之外的都叫別人)
低耦合:各人自掃門前雪,莫管他人瓦上霜(莫管!!!)
按鈕按下去之后,那概怎么辦呢?
放在回調里面去做,不管
至于回調里面做什么,不管。
用戶,用這個模塊的人,他想要干什么,用回調函數去實現
或者觸發事件,需要到應用層里面去實現
第二重:
高內聚:做好自己,寬容他人(軟件要有容錯性:當你的輸入產生一些錯誤的時候,要把他包容掉,剔除掉)
(比如這個接口要允許他輸入一個空指針,輸入出錯兼容掉)
低耦合:不管索取,只求奉獻(一個模塊不能提供所需要的功能,需要提供更多的功能以產生柔性)
第三重:
高內聚:運籌帷幄之中
(我只需要做好這個事情就可以了,我只要產生了想讓其他的模塊執行某功能的時候,九八這個信號把這個事件發出去,至于說人家接收不接受,不管。但是一旦對方接收,就算成功了,就產生了低耦合決勝千里之外的意思)
(button是一個對象,狀態機是一個對象,對象與對象之間就不會產生任何調用關系,耦合就更小了,小到中間只有一個無形的消息在傳播(一個小變量))
(每個對象之間僅僅只靠消息通信 )
低耦合:決勝千里之外

浙公網安備 33010602011771號