C的基本语法:函数,数组,指针
1 C语言
什么是C语言呢?
C语言是面向过程编程的高级语言;既可以像高级语言那样使用逻辑算法来编程,也具备低级语言面向计算机硬件编程;
C语言啥都可以干呢棒棒哒;既可以自己写逻辑算法、数据结构,然后封装成接口给别人调;也可以代码直接操作寄存器和外设;
那么平常吹水时老听水牛们说的面向对象编程又是啥意思呢?
面向对象编程指的是类对象,指将具有特定逻辑功能的多个函数包装成类,可以专注于模块化编程;
上游厂家为了减轻下游厂家的开发难度,将自己的逻辑代码全部封装成接口;下游厂家的程序开发只要专注调包,面向api编程;
1.1 C语言中的对象指的是数据所占用的物理内存,常指寄存器;和上面java里的类对象不同;
2 数据类型
2.1 数据存储
以arm架构为例,常见数据类型的存储空间如下;
2.1.1 1bytes:char,unsigned char;
2.1.2 2bytes:short int,unsigned short int;
2.1.3 4bytes:int,unsigned int,long int,float;
2.1.4 8bytes:long long int,double;
2.2 数据存储补充
2.2.1 C语言中字符串的结尾默认为null ;"Hello"=="H" 、"e"、"l"、"l"、"o"、"NULL" 共6个字节 ;
2.2.2 arm架构下的long int是4bytes;linux下long int为8bytes;本质是win32还是win64编译器的区别;
2.2.3 由于long long int 和double还是8bytes存储空间的,所以汇编的启动文件中才会有 ALIGN 8;
2.3 数据换算
1 MB = 2^10 KB = 2^20 bytes; 1 byte=8bit,单片机的寄存器都是以8bit为一个块来存储的,所以byte就是寻址的最小单位;
1 MHz = 10^3 KHz = 10^6 Hz; 秒,毫,微,纳,皮的时间单位进制也为1000;
2.4 逻辑运算 && || !
逻辑运算非假即真;
2.4.1 逻辑 &&:与0为0,常用来置0;
2.4.2 逻辑 || :或1为1,常用来置1;
2.4.3 逻辑 ! :逻辑取反;
2.4.4 在对多个式子进行逻辑&&的时候,如果式子中有一个式子为假,则编译器会判定逻辑为假直接返回;(有点废话的赶脚)
2.5 位运算 &,|,~,^,<<,>>
2.5.1 &,|,~,^ 对两个数据进行按位运算,^异或运算 相异为1,相同为0;
2.5.2 << 左移运算的数据低位自动补0;
2.5.3 >> 右移运算的数据高位如果是无符号则自动补0、如果是有符号位则可能随机补0补1;所以要小心有bug;
2.5.4 20240104补:位运算是将两个变量以2进制的形式列出然后进行位运算,符号只有一位,逻辑运算非0即真,符号&&有两位,注意区分;
2.6 赋值运算"&=","|=","^=","<<=",">>=","+=","*="...
赋值运算的运算符都在等号左边,"~="没有这个运算符;"a &= b" 等价于 "a = a&b" ;
赋值运算都是用来自身运算的么?先放着;
2.7 全局变量与局部变量
全局变量:定义在函数外的变量,存储在静态内存中,从程序运行开始存在,直到程序销毁;默认初始值为0;
局部变量:定义在函数内的变量,存储在堆栈中,默认初始值为该堆栈之前的值,使用时尽量软件初始化;
定义外设结构体时,定义在函数内部但是又没有初始化就直接使用的话,受随机值影响可能会跑不起来;
3 函数
作用:实现具体功能的代码块,使程序结构清晰;
重点:同数组一样,函数名就是函数的首地址,可直接赋值给指针而不用 &取址符;
3.1 函数声明
函数在调用之前需要先声明;先声明,后调用;
通常将函数定义写在.c文件中,然后把函数声明放在.h文件中;通过包含.h文件来调用函数;
头文件的作用是一方面是提供函数入口地址,一方面是让编译器编译的时候检查一遍函数声明;
//函数类型 函数名 (参数类型 参数名, ...) void func1_name( int param1, int param2); int func2_name( void ); //将函数定义写在main函数前,则可以省略函数声明; //函数声明的作用就是为了让main函数知道函数入口地址在哪里,函数定义写在main函数之前,那main函数也一样知道了入口地址;
3.2 函数定义
//函数类型就是函数的返回值类型,函数的类型可不就是函数的返回值类型么? //函数的参数类型可以省略参数名,不能省略参数类型; //函数内不能嵌套定义新的函数原型,可以调用其他函数; int add(int a,int b){ return a+b; }
3.3 函数调用
3.3.1 形参:被调函数声明或定义中出现的变量;作用域只在该函数中,函数结束后随函数销毁;
3.3.2 实参:主调函数即将传递的具体值;应该作用域只在主调函数中,随主调函数的结束而销毁,中断不算;
3.3.3 当数组名作为实参传递时,传递的是该数组的首地址,数组名就是数组第一个元素的地址;
int array[10]={0}; if( array==&array[0]) printf(" if等式是成立的;");
3.4 main函数的固定参数
void main( int argc, char * argv[ ] ) /*argc 参数记录命令行的命令和参数个数*/ /*argv 参数记录命令行的命令和参数个数的内容*/ /*具体的又忘记了,以后再补充吧*/
4 数组
4.1 一维数组
4.1.1 数组声明
int array[10]; /*整型数组array[],有10个int型对象*/ int *add[10]; /*给指针型数组留个位置*/
4.1.2 数组初始化
int day[7]={1,2,3,4,5,6,7}; /*声明数组的同时对所有变量初始化*/ int day[7]={1,2,3}; /*声明数组的同时对部分变量初始化,未初始化部分默认值为0*/ int day[7]={1, [3]=4,[5]=6,[6]=7}; /*声明数组的同时对部分变量初始化,未初始化部分默认值为0*/
4.1.3 数组使用
int array[10] ={1,2,3,4,5,6,7}; for(i=0;i<10;i++) printf( " array[ %d ] " ,array[i] ); /*数组的下标从0开始,只能逐个引用,编译器不会检查数组下标是否大于数组声明的个数*/
ps:数组的调用为传址调用,修改后的参数会同步给原函数;如下array[10]就会,value不会;
int read( int array[10] , int value ) ;
4.2 二维数组:表示数组的数组
4.2.1 数组声明
float Salary [5][10]; /*二维数组salary中有5个一维数组,其中每个一维数组有10个float型对象*/
4.2.2 数组初始化
float salary [2][3]={{7000,8000.8,9000},{10000,9500.50,12000}}; /*一 一 赋值初始化*/ float salary [2][3]={{7000,8000},{9000}}; /*部分初始化,未赋值部分初始化为0*/ float salary [2][3]={7000,8000,9000}; /*按顺序初始化salary[0][0], salary[0][1] salary[0][2], 然后初始化salary[1][0] salary[1][1] salary[1][2],未赋值部分初始化为0*/
4.2.3 数组使用
float salary [2][3]={7000,7500,8000,8500,9000,9500}; int i,j; float salary_sum=0; for(i=0;i<2;i++) for(j=0;j<3;j++) { salary_sum+=salary[i][j]; } /*二维数组的存储顺序是依次存放,先排完第一个一维数组的地址,然后依次排第二个一维数组的地址*/
4.2.4 sizeof
//虽然数组名也是地址,但是sizeof(数组名),返回的不是指针大小,而是数组大小;有点不合逻辑,先放着; int array_t[10]; int size = sizeof(array_t); //输出array_t数组大小:40 int ant_size = sizeof(&array_t); //输出指向array_t数组的指针大小:4; 64位系统如vscode下为8; int *p = array_t; printf("p:%02d\n",sizeof(p)); //输出指向array_t数组的指针大小:4; 64位系统如vscode下为8;
5 指针
指针是数据为地址的变量类型;要对指针地址内的数据进行操作,需要使用*间接运算符来指向数据;
int a = 5 ; int *p ; //声明指针,但是未赋值, int *p; 的意思是p是一个指针,p的值是地址,*p的数据是int类型 p = &a ; // int *a ,b , c ; 不等于 int *a ,*b , *c ;
int a; int *p = &a; //声明指针的同时赋值
5.1 使用事项
5.1.1 指针数据的间接运算
/******交换指针m与n 地址内的值******/ void swap(int *m,int *n) { int temp; temp = *n; *n = *m; *m = temp; }
/******ps:'*’称之为间接运算符的原因 ******/ int value1,value2; int *pointer; /******下面语句等价于 value1=value2 ******/ pointer = &value2; value1 = *pointer;
5.1.2 在函数原型或函数定义头文件中,由于数组名和指针均表示地址,所以可以互相替代;
int sum(int ar[],int n); /*表示形参ar是一个指针,指向int类型的数组*/ int sum(int *ar, int n); /*表示形参ar是一个指针,指向int类型对象*/
5.1.3 指针变量也像其他变量一样有自己的地址,地址直至销毁前不变;
int num; int *ptr =# printf("%p ,%p" , ptr ,&ptr); /*第一个%p是指针变量自己的地址;第二个%p是num变量的地址,ptr作为地址,取出地址内的地址;*/
5.1.4 指针的自加加 加的是指针所指向的数据类型的长度;
int * ptr; int total=0; total+=* ptr++; total+=* (ptr++); /*一元运算符*与自加加优先级相同,所以从右像左,先计算++ */ int array[10]; &array[2]==array+2; /*等式成立,地址array的值加上8个字节,int类型=4字节*/ array[3]==*(array+3); /*等式成立,此处是数值*/ /* array+2可以遍历数组地址*/ /* *(array+3)可以遍历数组内的数值 */
5.2 指针与二维数组
5.2.1 声明二维数组指针
int (* ptr)[3] ; /* 声明指针为二维数组指针,每个一维数组有3个对象;(*ptr)表示指针名字为ptr; */ int * ptr[3]; /*[]的优先级高于*,所以先结合为ptr[],数组个数为3,表示定义一个数组,数组对象是3个指向int型的指针*/
/*声明指针的数组*/
int array[2][3] ; int (*ptr)[3]; ptr=array; /*-- array+1 表示二维数组的首地址加上一个一维数组的大小,1个一维数组共3个int型对象12字节 --*/ /*-- array[0]+1 表示将二维数组的对象顺序排列为一维数组后,加上一个int型对象的大小,共4字节 --*/ *&array[0][0]==**ptr; /*等式成立*/ array[2][1]== *(*(ptr+2)+1); /*等式成立*/ ptr[2][3]==array[2][1]; /*等式成立*/
5.2.2 二维数组使用
int array[2][3]={0}; /*声明一个2行3列的数组*/ /*array[0]表示数组第一行的首地址,每行有3个int型对象*/ /*array[1]表示数组第二行的首地址,每行有3个int型对象*/ int i,j,total=0; for(i=0;i<2;i++) { for(j=0;j<3;j++) { //total+=*(array[i]+j);
//total+= *(*(array+i)+j);
total+=array[i][j]; } } /*不知道这里理解的对不对*/
5.3 函数指针:指向函数的指针;
声明一个特定函数的指针时,可以先写出函数的原型,然后给函数名加个( * );
char fruits[20]="orange papaya"; void upper(char *); //函数声明 void (*pfun)(char *); //函数指针声明; pfun = upper; //函数指针 赋值; upper(fruits); //函数调用,常见方式; (*pfun)(fruits); //函数调用,以函数指针的方式调用; //声明变量指针时,需要给指针定义一个标识符,以及指针指向的 数据类型; //声明函数指针,也需要给指针定义一个标识符,以及指针指向的 函数的返回值类型和参数;
5.3.1 括号和成员运算符(. ->)的优先级最高,是从左往右结合;其次是解引用和取址运算符( * &),从右往左运算 ;
由于运算符的优先级限制,所以声明函数指针时,才必须将*与指针标识符括起来:(* pfunc);
int check[6][8]; //声明数组check有6个元素,每个元素包含8个整型变量; int ** ptr; //声明一个指向指针的指针,被指向的指针指向int型;用来遍历像int *check[10];这样的数组; int * check[10]; //数组check有10个元素,每个元素都是指向int型的指针; int (*check)[10]; //(*check)表示该声明是指针的声明;是指向元素个数为10的数组的指针;数组元素为int型对象;二维指针声明; int * off[3][4]; //声明语句的主语是off[3][4]数组,数组元素是int型指针; int ( *uuf )[3][4]; //(*uuf)表示是指针uuf,指针指向[3][4]二维数组,数组元素为int型; int ( *uof[3])[4]; //*uof[3]是一个数组,数组内有3个指针;该指针是指向4个元素的int型数组; char * fump(int); //首先是函数fump(int),表示函数返回值是字符型指针; char (*frump)(int); //(*frump)是一个指针,(*frump)(int)指向函数的指针,函数的返回值为char; char (*flump[3])(int); //flump[3]是一个数组,(*flump[3])表示数组内有3个指针;该指针为指向函数的指针,函数的返回值类型为char;
5.4 字符串指针
/***字符串加了" ":为字符串 idle\0 申请了常量内存,然后返回该内存地址;***/ //常量内存只有读取权限,没有写入权限; //关于全局数据区、栈区、常量区以及其他的内存分区的区别,先放着; const char *name = "idle";
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?