天天看点

【C语言】C语言笔记整理一、内存中的一些概念二、语法三、链表四、字符串

目录:

  • 一、内存中的一些概念
    • 1.栈与堆的区别:
    • 2.malloc()函数
    • 3.内存泄漏(Memory Leak)
    • 4.补码
  • 二、语法
    • 1.逗号表达式
      • 表达式说明:
    • 2.switch语句
      • 表达式说明:
    • 3.结构体
      • ①基本结构体说明
      • ②访问结构体成员的方式
      • ③函数间传递结构体
  • 三、链表
    • 1.链表与数组的对比:
    • 2.链表的基本概念
  • 四、字符串
    • 1.字符与字符串的区别
    • 2.string.h库重要函数

一、内存中的一些概念

1.栈与堆的区别:

name describe difference
栈(stack) 存放临时/局部变量 由编译器自动分配/释放内存
堆 (heap) 是自由存储区,存放动态数据,例如malloc()申请的空间 一般由程序员分配/释放内存

栈(stack):

//example
int a;
char s[] = "adc";
char *p;
char *p1 = "123456";
           

堆(heap):

//example
int * p = (int*)malloc(4);
           

2.malloc()函数

  • malloc (Memory allocation)函数只有一个形参,并且形参是整形.
  • 上述例子中的malloc(4)中的“4” 表示程序分配4个字节.
  • malloc函数只能返回第一个字节的地址
  • p本身所占的内存是静态分配的,p所指向的内存是动态分配的.
  • 函数原型:void *malloc(unsigned int size)
  • 由于malloc()函数只返回的是第一个字节的地址, 所以导致p所指向的变量并不知道有多少个字节 ,所以上述例子中(int *)malloc(4)中的强制类型转换(int *)的作用为:将malloc()函数返回的地址转换为一个整形变量的地址.

free函数:释放动态内存

  • 函数原型 void * free (void *p) //释放其指向的动态内存

3.内存泄漏(Memory Leak)

是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

4.补码

  • 计算机中的数据存储方式为补码形式
1)采用补码的原因或好处如下,采用补码运算具有如下两个特征:
  • ①因为使用补码可以将符号位和其他位统一处理,同时,减法也可以按加法来处理,即如果是补码表示的数,不管是加减法都直接用加法运算即可实现。
  • ②两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。
2)这样的运算有两个好处:
  • ①使符号位能与有效值部分一起参加运算,从而简化运算规则。从而可以简化运算器的结构,提高运算速度; ( 即减法运算可以用加法运算表示出来)
  • ②加法运算比减法运算更易于实现。使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计。

举例一个4位数组,说明原码反码补码的关系:【第一位为符号位:0正 1负】

数字 原码 反码 补码
5(正数) 0101 0101 0101
-6 (负数) 1110 1001 1010

「正数的原码反码补码都相等;负数,反码等于原码符号位不变,其余各位取反,补码等于反码加1」

//example

当 计算 5-6 时,我们可以看做 5+(-6) ,运算时均化成补码形式进行运算
由上表得到5与(-6)的补码
即: 5+(-6)  -->   0101(补码)+1010(补码) = 1111(补码)
           

由上计算出的数据为 1111(补码),所以想得到原始数据,需要将 补码-1,除符号位均取反

得到:1001 --> -1【验证计算5-6 = -1,数据正确】

  • 补码+补码=补码,本质未改变

二、语法

1.逗号表达式

表达式说明:

表达式1,表达式2,表达式3,…… ,表达式n 

逗号表达式的要领: 
(1) 逗号表达式的运算过程为:从左往右逐个计算表达式。 
(2) 逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。 
(3) 逗号运算符的优先级别在所有运算符中最低。 
(4) 与赋值运算符一起,且未加括号时,先执行赋值运算符 
(优先级:括号>赋值运算符>逗号)
           
//example
void main(void) 
{ 
	int x,y,z; 
	x=y=1; 
	z=x++,y++,++y; 
	printf(“%d,%d,%d\n”,x,y,z); 
} 

           
[A]2,3,3 [B]2,3,2 [C]2,3,1 [D]1,1,1 
正确答案选: C
           

2.switch语句

表达式说明:

// switch语句
switch(表达式)
{
	case 常量表达式1:语句1;//break;
	case 常量表达式2:语句2;//break;
		...
	case 常量表达式n:语句n;//break;
	default :	语句n+1;  //break;
}
           

3.结构体

①基本结构体说明

struct 结构名   //结构体名可理解为一个标签名
{
	成员列表;
	
}变量名;
           

推荐的方式①:

/* 创建人属性 结构体  标签:Person */
struct Person
{
	int age;  		//年龄
	float stature;	//身高
	char sex[10];	//姓名
};

/* 定义“小王” 人物属性 并 进行赋初值 */
struct Person XiaoWang = {21, 178.5, "male"};
           

推荐的方式②:

/* 用typedef创建 新类型 */
typedef struct Per
{
	int age;  		//年龄
	float stature;	//身高
	char sex[10];	//姓名
}Person; 

/* 定义“小王” 人物属性 并 进行赋初值 */
Person XiaoWang = {21, 178.5, "male"};
           

C语言结构体定义有多种方式可参考::C 结构体 菜鸟教程.

②访问结构体成员的方式

方式1.  结构体变量名.成员名
方式2.  指针变量名->成员名
           

首先定义:

/* 用typedef创建 新类型 */
typedef struct Per
{
	int age;  		//年龄
	float stature;	//身高
	char sex[10];	//姓名
}Person; 

int main(void)
{
	Person XiaoWang = {21, 178.5, "male"};
	Person * pst = &XiaoWang;
	
	return 0;
}
           

方式1

XiaoWang.age = 18;
           

方式2

其含义为: pst所指向的那个结构体变量中的age这个成员

pst->age = 18;  //内部转换成(*pst) .age = 18;
           

③函数间传递结构体

首先定义JY901_t (t类型结构体ype)

定义JY901 结构体变量

struct JY901_t
{
	float Acc[3]; 		  //加速度
	float Gyro[3]; 			//角速度
	float Angle[3];			//角度
	short Mag[3];
	float Temperature;	//温度
};

struct JY901_t JY901 = {0}; //JY901真实值结构体
           

错误示例:↓ ↓↓

/* JY901 数据转换 */
void JY901_Convert(struct JY901_t  Arr) //
{
		static u8 i = 0;
		for(i = 0;i < 3;i++){	
				Arr.Acc[i] = (float)stcAcc.a[i]/32768*16;
				Arr.Gyro[i] = (float)stcGyro.w[i]/32768*2000;
				Arr.Angle[i] = (float)stcAngle.angle[i]/32768*180;
				Arr.Mag[i] 	  = stcMag.h[i];
		}
		pArr.Temperature = (float)stcAcc.T/100;
}

static void time_out(void* parameter)// 定时器1超时函数  进行JY901模块数据转换
{
    rt_enter_critical(); /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
    
	JY901_Convert(JY901); //调用数据转换函数

    rt_exit_critical();	/* 调度器解锁 */
}

           

传入JY901_Convert() 函数的 结构体形参为 一个局部静态变量。当调用该函数时,并不能做到数据转换,因为在子函数执行结束后,局部静态变量会被系统释放掉,所以得到的值为空。

  • 因此,函数传参尽量用指针来代替。

正确示例:↓ ↓↓

/* JY901 数据转换 */
void JY901_Convert(struct JY901_t * pArr) //传入结构体指针类型
{
		static u8 i = 0;
		for(i = 0;i < 3;i++){	
				pArr->Acc[i] = (float)stcAcc.a[i]/32768*16;
				pArr->Gyro[i] = (float)stcGyro.w[i]/32768*2000;
				pArr->Angle[i] = (float)stcAngle.angle[i]/32768*180;
				pArr->Mag[i] 		= stcMag.h[i];
		}
		pArr->Temperature = (float)stcAcc.T/100;
}

static void time_out(void* parameter)// 定时器1超时函数  进行JY901模块数据转换
{
    rt_enter_critical();/* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */

	JY901_Convert(&JY901);//调用数据转换函数  

    rt_exit_critical();	/* 调度器解锁 */
}

           

由下图可得到 JY901结构体所占用字节数为 48

从内存占用的角度来讲:我们进行函数传参时应该使用指针传递,因为在32位系统中一个指针长度仅 4 个字节数

【C语言】C语言笔记整理一、内存中的一些概念二、语法三、链表四、字符串

指针传参好处:

  • 快速的传递数据
  • 耗用内存小
  • 执行速度快

三、链表

1.链表与数组的对比:

name advantage disadvantage
数组 存取速度快,查找方便 需要一个连续的内存空间,插入和删除元素效率低
链表 插入删除元素效率高 查找某个位置元素效率低

2.链表的基本概念

①首节点
  • 存放第一个有效数据的节点
②尾节点
  • 存放最后一个有效数据的节点
③头节点
  • 头节点的数据类型与首节点类型一致
  • 头节点是首节点前面的那个节点
  • 头结点并不存放有效数据
  • 设置头节点的作用为方便对链表的操作
④头指针
  • 存放头结点地址的指针变量
typedef struct Node
{
   int data;
   struct Node *next;
}LinkList_Type;
           
//创建表头 返回表头
LinkList_Type *creatList(void)
{
	LinkList_Type *headNode = (LinkList_Type*)malloc(sizeof(LinkList_Type));

	headNode->next = NULL;

	return headNode;
}
           
//创建节点  
LinkList_Type * creatNode(int data)
{
	LinkList_Type * newNode = (LinkList_Type *)malloc(sizeof(LinkList_Type));

	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}
           
//打印链表  遍历输出
void printList(LinkList_Type * headNode)
{
	LinkList_Type *p = headNode->next;
	while(p != NULL)//如果当前位置的节点不为空
	{
		printf("%d \t",p->data);
		p = p->next;//指向下一个节点
	}
	printf("\n");
}
           
//头插法:插入节点【栈】后进先出          ps:尾插法 【队列】 先进先出
void insertNode_byHead(LinkList_Type * headNode,int data)
{
	LinkList_Type * newNode = creatNode(data);
	newNode->next = headNode->next;//指向原本headNode 所指向的下一个
	headNode->next = newNode; //替代原本headNode指向的节点
}
           
/* 删除指定的节点 */
void deleteNode_byNode(struct node* head,int pos)
{
    struct node* frontNode = head;     //指定节点的前一个节点
    struct node* posNode = head->next; //指定节点
    static int count = 0; //计数
    if(NULL == head->next){
        printf("This Linklist is empty.\n");
        return;
    }
    else{
        while(count != pos){//当未达到指定节点
            frontNode = posNode;
            posNode = posNode->next;
            count ++;
            if(NULL == posNode){
                printf("No find the point data node\n");
                return;
            }
        }
        frontNode->next = posNode->next;
        free(posNode);//释放指定节点
    }
}
           
//指定数据删除
void deleteNode_byAppoin(LinkList_Type* headNode,int posData)
{
    LinkList_Type *posNode = headNode->next;//指定节点从头结点的下一个开始
	LinkList_Type *posNodeFront = headNode; 
	if(posNode == NULL){
		printf("无法删除,链表为空\n");
	}
	else {
		while(posNode->data != posData)//不等于指定的数据,继续向下找
		{
			posNodeFront = posNode;
			posNode = posNode->next;
			if(posNode == NULL)//找到表尾,即未找到
			{
				printf("没有找到相关信息,无法删除\n");
				return;//结束
			}
		}
		posNodeFront->next = posNode->next;//指定节点posNode地址跳过
		free(posNode);//释放内存
	}

}
           
/* 主函数测试 */
int main(void)
{

    printf("hello world!\n");         
    struct node * head = creatList();//创建表头
    insertNode_byHead(head,1);
    insertNode_byHead(head,2);
    insertNode_byHead(head,3);
    insertNode_byHead(head,4);

    deleteNode_byData(head,1);

    deleteNode_byNode(head,2);
    printList(head);
 
    return 0;
}
           

四、字符串

1.字符与字符串的区别

名称 符号 内容 输出格式 标识
字符 ’ ’ 单引号 单个字符 %c
字符串 " "双引号 多个字符 %s ‘\0’ 作为结尾
字符串的本质为:
  • 一个字符数组
char string[20] = "hello world!";

char name[] = "张三";    //省略长度

char name_array[][20] = {"张三","李四"};//二维数组

           
// 首先因为[]优先级比*高,所以name先与[]结合,成为一个数组,再与*结合,说明数组中的元素都为指针类型,
// 再与char结合,说明指针指向的内容是字符型的.所以name是一个指向由字符型数据组成的数组的指针。
           

2.string.h库重要函数

  • ①字符串格式化命令:sprintf()
//把格式化的数据写入某个字符串缓冲区。
	
	函数原型:int sprintf( char *buffer, const char *format, [ argument] … );
           

MCU常常无法直接输出浮点型,可以使用sprintf函数将数据打包后再输出。

char str[50];
	sprintf(str , "Acc:%.3f %.3f %.3f" , JY901.Acc[0],JY901.Acc[1],JY901.Acc[2]);
	LOG_H(str);
           

下图可见单片机输出了浮点型数据。

【C语言】C语言笔记整理一、内存中的一些概念二、语法三、链表四、字符串
  • ②比较:strcmp()
函数原型:extern int strcmp(const char *str1,const char *str2);
	
	若str1=str2,则返回零;
	若str1<str2,则返回负数;
	若str1>str2,则返回正数。
	两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。
           
//若argv[1] == "on" 返回 0
if( !strcmp(argv[1],"on") ){
	gyroscope_led_array[3] = 0x00;
	LOG_H("Operation is successful! gyroscope_led on\n");
}
           
  • ③连接:strcat()
char hi[20] = "hello ";
char *name = "world!";
printf("%s \n",strcat(hi,name));
           
输出结果:hello world!
           
  • ④拷贝:strcpy()
函数原型:char * strcpy(char * strDest,const char * strSrc);
           
char str[20] = "hello world!";
printf("%s \n",strcpy(str,"hey,guys!"));
           
输出结果:hey,guys!
           
  • ⑤获取长度:strlen()
函数原型:extern unsigned int strlen(char *s);
           
  • ⑥转换大写:strupr()
函数原型:extern char *strupr(char *s);
           
  • ⑦转换小写:strlwr()
函数原型:extern char *strlwr(char *s);
           

继续阅读