C语言篇复习概述

文章目录

基础概述

关键字

volatile

防止被编译器优化,修饰变量

  • 中断服务程序中修改的供其他程序检测的变量需要加volatile;

  • 寄存器类型的变量

  • 多次读写的变量

  • 多任务环境下各任务间共享的标志应该加volatile;

这个关键字一般不使用,在代码后期优化的时候使用

static

修饰变量:

  1. 变量定义在函数内部,其变量值初始化一次不会随函数销毁,存储在全局静态变量区。
  2. 变量定义在函数外部,修饰该变量只能在该文件内部使用,不能通过extern给其他文件使用。

修饰函数:

  1. 静态函数只能在声明它的文件中可见,其他文件不能引用该函数
    不同的文件可以使用相同名字的静态函数,互不影响。

static是一个很有用的关键字,使用得当可以使程序锦上添花。当然,有的公司编码规范明确规定只用于本文件的函数要全部使用static关键字声明,这是一个良好的编码风格。

在C语言中,为什么static变量只初始化一次?

对于所有的对象〈不仅仅是静态对象),初始化都只有一次,而由于静态变量具有"记忆"功能,初始化后,一直都没有被销毁,五段三区都会保存在内存区域的静态变量存储区中,所以不会再次初始化。存放在静态区的变量的生命周期一般比较长,它与整个程序"同生死、共存亡",所以它只需初始化一次。而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁。

extern的作用是什么?

  1. 在需要使用外部变量或者函数的地方声明+定义该文件的头文件,使用外部变量
  2. 在.h文件中extern变量或者函数以后,其他文件直接调用头文件就可以使用extern声明的变量或者函数

extern"C”的作用是什么?

  • extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上exterm "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

const

代码优化阶段

修饰对象成只读,防止误修改,内存只读存储在符号表里面成为常量

在讲解const修饰符之前,首先说明const修饰符的几个典型作用。

  • const类型定义:指明变量或对象的值是不能被更新的,引入目的是为了取代预编译。
  • 可以保护被修饰的内容,防止其被意外地修改,增强程序的健壮性。
  • 编译器通常不为普通const常量分配存储空间,而是将它保存在符号表中,这使它成为一个编译期间的常量,没有了存储与读内存的操作,它的效率也很高。
  • 可以节省空间,避免不必要的内存分配。

1.const修饰符在函数体内修饰局部变量

- 修饰局部常量

const int 5 == int const 5 //必须赋初值

是等价的。在编程的过程中一定要清楚地知道const修饰的对象是谁,在这里修饰的是n,和int没有关系。const要求它所修饰的对象为常量,不能被改变,同时也不能够被赋值,所以下面这样的写法是错误的。

const int n;
n=0

- 修饰指针常量

const 修饰指针

常量指针
const int* p
其访问*p 不能修改指针的值,只能通过引用的变量修改
int a = 10;
*p = &a;
a = 5 ;
*p = &b;

指针常量
int * const p
其访问的地址不能修改,但是访问地址的值可以修改
*p = 5;
int a = 10;
*p = &a;
a = 5 ;

const int *p; == int const *q;

在上面的声明中,p和q都被声明为const int类型的指针

int *const q =&n;

int类型的const指针

top:以上的p和q都是指向const int类型的指针,也就是说,在以后的程序中不能改变*p的值而r是一个const 指针,在声明的时候将它初始化为指向变量n(即“r=&n ; ")之后,r的地址将不允许再改变,但r的值是可以改变的。在此,为了判断const的修饰对象
介绍一种常用的方法:以
为界线,如果const位于的左侧,那么const就是用来修饰指针所指向的变量的,即指针指向常量;如果const位于的右侧,那么const就是修饰指针本身的,即指针本身是常量。接下来看下面的代码。
在这里插入图片描述
简单分析一下,因为r指向的是ss的地址,所以修改r指向的地址单元的值的同时,ss的值也随之变化。

2.const在函数声明时修饰参数

作为参数

  • 防止形参在函数里面被修改,提前通过结构体定义好 常用在Linux内核里面作为结构体变量初始化作为输入型参数
  • 比如socket网络编程的bind函数

在这里插入图片描述

作为返回值

  • 返回值跟变量声明一致
const char GetString()  //定义一个函数
char *str= GetString() //错误,因为str没有被 const修饰
const char *str=GetString() //正确

const 数组
常量数组

3.const作为全局变量

c语言中的存储类型有auto, extern, register, static

C语言一切变量或者函数具有两个存储类型type数据类型的属性

  • auto确省条件下,默认申请的变量与函数都是auto存储类型,就是程序使用完这个变量会释放。
  • extern,使用其他文件里面的函数或者变量的外部变量。
  • register,寄存器变量跟运行环境有关,嵌入式行业硬件不一样,比如32开发板,imx6ull,rk3568,全志v3s这些不同的硬件环境,其寄存器数量不一样。为什么申请存储类型为寄存器,对经常访问的变量方便快速访问。
  • static,在函数里面声明,只能初始化一次,下次进入函数不被初始化,在函数体内修改声明变量退出函数也会保留这个内存里面的数值。在函数外作为全局声明表面不能被外界访问。

补充static

  • static 修饰变量。在修饰全局变量的时候,作用于限定于被定义的文件中,使用 extern 也不能访问; 在修饰局部变量的时候,在函数体中定义,只能用于该函数,即使使用该函数结束,静态变量也不会被销毁,下次可以继续使用,即缩小了访问范围但是延长了访问时间。
  • 修饰函数的时候,函数的作用于仅限于本文件,故也叫内部函数,和修饰全局变量类似。这就是为什么 linux 内核中所有的函数和全局变量都声明static 的原因,linux 内核代码很多,有几万个文件,为了防止文件重名造成冲突,所以就干脆声明为静态的。
  • 存储区域:静态存储区(全局储存区)如果是初始化的静态变量则保存在静态存储区的DATA段,如果是未初始化的静态变量则保存在静态存储区的BSS
  • 使用场景:我们需要将函数内部定义的变量保存下去或者是作为函数的返回值时,一般菜鸟程序员比如我直接申请全局变量,但是文件多的时候就会造成重入现象的发生。

new、delete、malloc、free关系

  • C++中new、delete。new动态申请内存,创建一个对象,执行构造函数对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。delete 会调用对象的析构函数

  • new 对应 free 只会释放内存,new 调用构造函数。malloc与 free 是 C++/C 语言的标准库函数new/delete 是 C++的运算符。

  • mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。
    malloc() 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。如果希望在分配内存的同时进行初始化,请使用 calloc() 函数。【返回值】分配成功返回指向该内存的地址,失败则返回 NULL。

  • new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。

C中struct与union的区别

  • struct是结构体,其是一种复合的数据类型可以定义不同的数据,其内存长度是内部定义的数据类型长度之和,union联合体其长度等于内部定义最大的数据长度,每时刻只能访问一种数据类型,不然程序中最后访问的数据类型会自动覆盖前面访问的数据类型,联合体常用于通信,数据接收不知道为何种变量的时候,把其存放在char数组中,在联合体定义int、float变量,然后直接访问就可以

编程实例union的使用
串口解析接收的两个float变量

float gpslongitude,gpslatitude;
u8 l,k;

 union a
      {
       float sdata;
       unsigned   char  cdata[4];

      }b;
			
union c
      {
       float cdata;
       unsigned   char  ddata[4];

      }d;
union c d;
int i;
void USART3_IRQHandler(void)   //串口接收数据且接收的数据包含头尾标识为例。
{//状态位只能有硬件置位也就是置1说明中断产生,软件只能读和清零
	if(USART_GetITStatus(USART3,USART_IT_RXNE) != RESET) //中断产生 USART_GetITStatus串口状态检测函数
	{
	USART_ClearITPendingBit(USART3,USART_IT_RXNE); //清除中断标志
  Uart3_Buffer[Uart3_Rx] = USART_ReceiveData(USART3); 
//	UsartPrintf(USART_DEBUG, "%s\n",&Uart3_Buffer[Uart3_Rx]);
	Uart3_Rx++;
//			UsartPrintf(USART_DEBUG, "Uart3_Buffer");
		if(Uart3_Buffer[Uart3_Rx-1]=='B'||Uart3_Rx == Max_BUFF_Len)
		{	UsartPrintf(USART_DEBUG, "进入B\r\n"); 
					if(Uart3_Buffer[0]=='A')
					{		 	UsartPrintf(USART_DEBUG, "进入A\r\n"); 
						b.cdata[0]=Uart3_Buffer[1];
						b.cdata[1]=Uart3_Buffer[2];
						b.cdata[2]=Uart3_Buffer[3];
						b.cdata[3]=Uart3_Buffer[4];		
    gpslongitude =	b.sdata;//union的特点共用内存			
						for(i=0;i<50;i++)
								{
									Uart3_Buffer[i] =0;
								}
						Uart3_Rx=0;
						}
					else
								{
							Uart3_Rx=0;
								}
							}
		if(Uart3_Buffer[Uart3_Rx-1]=='D'||Uart3_Rx == Max_BUFF_Len)
		{
					UsartPrintf(USART_DEBUG, "进入D\r\n");
					if(Uart3_Buffer[0]=='C')
					{			UsartPrintf(USART_DEBUG, "进入C\r\n"); 
								d.ddata[0]=Uart3_Buffer[1];
								d.ddata[1]=Uart3_Buffer[2];
								d.ddata[2]=Uart3_Buffer[3];
								d.ddata[3]=Uart3_Buffer[4];
								gpslatitude =	d.cdata;			
								for(i=0;i<50;i++)
								{
									Uart3_Buffer[i] =0;
								}
								Uart3_Rx=0;
						}
					else
								{
									Uart3_Rx=0;
								}
	 	}
				if(Uart3_Buffer[Uart3_Rx-1]=='F'||Uart3_Rx == Max_BUFF_Len)
		{
					if(Uart3_Buffer[0]=='E')
					{		 
								    l=Uart3_Buffer[1];
								    k=Uart3_Buffer[2];			
								for(i=0;i<50;i++)
								{
									Uart3_Buffer[i] =0;
								}
								Uart3_Rx=0;
						}
					else
								{
									Uart3_Rx=0;
								}
					}
					
				}
	}





a++与++a区别

  • a++会先将自己赋值给一个临时存储变量空间,再进行自加,当前运算值不变
int p=10,c; c = p++;问当前c值10
  • ++a直接自加效率更高。

sizeof

  • 是C语言的一种单目操作符,如C语言的其他操作符++、–等。它并不是函数与关键字,其返回值是求的变量的数据类型大小。其对结构体求大小也需要考虑字节对齐。

结构体字节对齐

判断16位还是32位的系统

不使用sizeof,如何求int占用的字节数?

宏定义为指针

#include <iostream>
using namespace std;
#define my_sizeof(L_Value)  (char* )(&L_Value + 1) - (char* )&L_Value 
int main()
{
    int i;

    cout << my_sizeof(i) << endl;

    return 0;
}

sizeof和strlen有什么区别?

  • 一个在编译阶段确定,一个在运行阶段确定
  • sizeof计算大小,strlen计算长度
    在这里插入图片描述

define

  • 定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。

预处理

  • 预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include 包含的文件代码,#define 宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
  • c 编译系统在对程序进行通常的编译之前,先进行预处理。c 提供的预处理功能主要有以下 三种:
    1)宏定义 2)文件包含 3)条件编译
  • C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤,是预编译的阶段。
    C语言执行的阶段,预编译-编译-汇编-链接
    其中预处理就是处理这些预处理指令
    比如宏定义的替换,条件编译的选择

C语言宏中"#“和”##"的用法

  • #把宏参数变为一个字符串,用##把两个宏参数贴合在一起
    参考

ifndef、ifdef、endif

  • 单片机的时候长期用

预处理器标识#error的目的是什么?

  • 配合ifdef输出已经定义的错误消息。

定义常量谁更好?# define还是const?

  • define只是用来进行单纯的文本替换,define常量的生命周期止于编译期,不分配内存空间,它存在于程序的代码段,在实际程序中,它只是一个常数;而const常量存在于程序的数据段,并在堆栈中分配了空间, const常量在程序中确确实实存在,并且可以被调用、传递。
  • const常量有数据类型,而define常量没有数据类型。编译器可以对const常量进行类型安全检查,如类型、语句结构等,而define不行。

typedef

  • typedef 关键字,您可以使用它来为类型取一个新的名字
    typedef 与 #define 的区别
  • 1、typedef是给数据类型起别名、define不能。
  • 2、作用域不同
  • 3、对指针的操作不同

如何使用define声明个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECOND_PER_YEAR (60*60*24*365)UL

# include< filename. h>和# nclude" filename.h"有什么区别?

  • 对于include <filename. h>,编译器先从标准库路径也就是编译器安装的位置自带的头文件开始搜索flename.h,使得系统文件调用较快。
  • 而对于#include"Mflename.h"",编译器先从用户的工作路径也就是自己编写代码的头文件开始搜索filename.h,如果没有找到再去找系统的路径,使得自定义文件较快。

头文件的作用有哪些?

  • 1.通过头文件来调用库功能。出于对源代码保密的考虑,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口是怎么实现的。编译器会从库中提取相应的代码。
  • 2.头文件能加强类型安全检查。当某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,大大减轻程序员调试、改错的负担。

在头文件中定义静态变量是否可行,为什么?

不可行,如果在头文件中定义静态变量,会造成资源浪费的问题,同时也可能引起程序错误。因为如果在使用了该头文件的每个C语言文件中定义静态变量,按照编译的步骤,在每个头文件中都会单独存在一个静态变量,从而会引起空间浪费或者程序错误所以,不推荐在头文件中定义任何变量,当然也包括静态变量。

有条件引用

#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

写一个"标准"宏MIN,这个宏输入两个参数并返回较小的一个?

#define MIN(A,B) ((A) <= (B) ? (A) : (B))

内存

内存分配的类型:

在这里插入图片描述

在C/C++中内存分为5个区,分别为栈区、堆区、全局/静态存储区、常量存储区、代码区。

  • ①栈区 —— 局部变量 —— 向低地址生长 —— 自动释放 —— 其操作方式类似于数据结构中的栈。

  • ②堆区 —— 向高地址生长 —— 手动分配、释放的存储区 —— malloc,free ——它与数据结构中的堆是两回事,分配方式倒是类似于链表

  • ③全局/静态存储区static —— 全局变量,静态变量,程序运行结束后自动释放

  • ④常量存储区const —— 常量字符串储存在这里。储存在常量区的只读不可写。程序运行结束后自动释放

  • ⑤代码区 —— 存放函数体的二进制代码。

  • ——> const修饰的全局变量也储存在常量区;

    ——> const修饰的局部变量依然在栈上

静态内存分配:编译时分配。包括:全局、静态全局、静态局部三种变量。

动态内存分配:运行时分配。包括:栈(stack): 局部变量。堆(heap): c语言中用到的变量被动态的分配在内存中。(malloc或calloc、realloc、free函数)

全部可以划分为五区三段
参考
在这里插入图片描述

堆与栈有什么区别?

1.申请方式

stack:栈;由系统自动分配,自动开辟空间
heap:由程序员自己申请并指明大小,c中malloc,c++中new。如p1=(char*)malloc(10);p2=(char*)new(10);但需要注意的是p1,p2本事是在栈中的

2.申请后系统的响应

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
堆:首先操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外对于大部分系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆节点大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

3.申请大小的限制

栈:在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域。所以栈的栈顶地址和最大容量是系统预先设定好的。在windows下栈的大小是2M.因此能从栈获得的空间比较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是是由于系统用链表来存储空闲内存地址的,所以是不连续的。而链表的遍历方向是由低地址到高地址。堆得大小受限于计算机系统中有效的虚拟内存大小。相比较而言堆获得的空间比较灵活,也比较大。

4.申请效率的比较

栈:由系统自动分配,速度较快,但程序员是无法控制的。 堆:由new分配的内存,一般速度比较慢,而且比较容易产生内存碎片,不过用起来最方便。

5.堆和栈中的存储内容

栈:在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数。在大多数c编译器中,参数是由右往左压栈的,然后是函数中的局部变量。静态变量是不入栈的。当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,,也就是主函数的下一条指令,程序由该点继续执行。
堆:一般是在堆的头部用一个字节存放堆得大小,其他内容自己安排。

6.存取效率的比较

1 char str1[]=“aaaaaa”; 2 char *str2=“cccccc”;
第一行是在运行时刻赋值的,第二行是在编译时就已经确定的存放在常量区,但在以后的存取过程中,在栈上的数组比指针指向的字符串快。

C语言里,哪些变量是存放在堆里?哪些是存放在栈里?

  • 堆区:全局变量,静态变量,malloc函数。
  • 栈区:函数的参数值,局部变量。

栈在C语言中有什么作用?

  • 1.C语言中栈用来存储临时变量,临时变量包括函数参数和函数内部定义的临时变量。函数调用中和函数调用相关的函数返回地址,函数中的临时变量,寄存器等均保存在栈中,函数调动返回后从栈中恢复寄存器和临时变量等函数运行场景。
  • ⒉.多线程编程的基础是栈,栈是多线程编程的基石,每一个线程都最少有一个自己专属的栈,用来存储本线程运行时各个函数的临时变量和维系函数调用和函数返回时的函数调用关系和函数运行场景。**操作系统最基本的功能是支持多线程编程,支持中断和异常处理,每个线程都有专属的栈,中断和异常处理也具有专属的栈,栈是操作系统多线程管理的基石。

C语言函数参数压栈顺序是怎样的?

C语言参数入栈顺序的好处就是可以动态变化参数个数。自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。

C语言压栈的调用顺序是怎样的?

栈:在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数。在大多数c编译器中,参数是由右往左压栈的,然后是函数中的局部变量。静态变量是不入栈的。当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,,也就是主函数的下一条指令,程序由该点继续执行。

什么是内存泄漏?

简单地说就是申请了一块内存空间,使用完毕后没有释放掉。
它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

如何判断内存泄漏?

  • 根源上解决:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难
  • 常见的工具插件,如ccmalloc、 Dmalloc、 Leaky等等。

简述编译运行一段代码的过程

源程序是指未经编译的,按照一定的程序设计语言规范书写的,人类可读的文本文件,源程序就是所写好的代码。可执行程序,即常说的.exe程序,可以执行程序,完成计算机功能。在C语言中,.c文件就是所谓的源文件。源程序到可执行程序的过程。在这个过程中,会发生如下的变化:
.c文件生成.obj文件的过程,称为编译,.obj文件生成到.exe文件的过程,称为链接。

.obj文件就是一个是程序编译生成的二进制文件,当.exe文件生成以后.obj文件就会被删除。事实上,.c文件生成.exe文件的过程总共是经历了预处理,编译,汇编,链接,这四个过程。

添加链接描述

静态链接和动态链接有什么区别

  • 静态链接是指把要调用的函数或者过程直接链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。静态链接的缺点是:当多个程序都调用相同函数时,内存中就会存在这个函数的多个复制,这样就浪费了内存资源
  • 动态链接是相对于静态链接而言的。动态链接调用的函数代码并没有被复制到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的动态链接库Q(dynamic link library,d)之间建立链接关系。当要执行调用,dll文件中的函数时,根据链接产生的重定位信息,操作系统才转去执行,dl文件中相应的函数代码。
  • 静态链接的执行程序能够在其他同类操作系统的机器上直接运行。例如,一个exe文件是在Windows2000系统上静态链接的,那么将该文件直接复制到另一台Windows2000的机器上,是可以运行的。而动态链接的执行程序则不可以,除非把该exe文件所需的dll文件都一并复制过去,或者对方机器上也有

C语言中内存分配的方式有几种?

  • 静态存储区分配 内存分配在程序编译之前完成,且在程序的整个运行期间都存在,例如全局变量、静态变量等
  • 栈上分配 在函数执行时,函数内的局部变量的存储单元在栈上创建,函数执行结束时这些存储单元自动释放。
  • 堆上分配

函数

函数参数传值与传参

形参与实参的区别

  • 实际定义的就是实参,没有传地址,函数定义的是形参,没有传地址

需要注意的是传值和传址的区别:

  • 传值是一种简单的对于实参的复制,形参有自己的存储空间,所以在函数中对于形参的操作不会影响实参
  • 而传址传递的是地址,实参和形参之间共享同一存储空间,在函数中对于形参的操作同样影响到实参

数组作为函数参数

  • 因此在写函数的时候,要清楚地知道需要采用传值还是传址。接下来通过下面的一段代码来了解数组作为函数参数的
    在这里插入图片描述
    在主函数中的printf打印的数组实参是发生了排序,因为
  • 数组名作为函数参数,传入的是地址能改变原有定义的数组
    伪代码定义与调用
int sort(int a[],int n);

int main(){
	int arr[5]={1,3,5,9,7};
	sort(arr,5);
}

指针作为函数参数

  • 与数组一样传递实参

函数指针作为函数参数

定义函数指针的方式

  • void (*FunP)(int x)
  • typedef void (*FunType)(int x) 这样如果以后想定义一个指向这种函数的指针,就可以直接 FunType p 来实现了

回调函数

  • 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

函数参数入栈顺序

#include

void foo(int x, int y, int z)
{
    printf("x = %d at [%X]n", x, &x);
    printf("y = %d at [%X]n", y, &y);
    printf("z = %d at [%X]n", z, &z);
}

int main(int argc, char *argv[])
{
    foo(100, 200, 300);
    return 0;
}


运行结果:

x = 100 at [BFE28760]
y = 200 at [BFE28764]
z = 300 at [BFE28768]

  • 从右向左依次入栈,由五段三区知道,栈stack由高地址向低地址生长,C程序栈底为高地址栈顶为低地址因此上面的实例可以说明函数参数入栈顺序的确是从右至左的,因为向低地址前进为生长方向

数组

int a[10] = {0};//整形数组需要初始化
char a[] ={“hello world”};//不用初始化sizeo(a)=13 空格+/0
在这里插入图片描述
在这里插入图片描述

指针数组和数组指针

数组指针就是指向数组的指针,它表示的是一个指针,这个指针指向的是一个数组,它的重点是指针。例如,,int ( *pa)[8]声明了一个指针,该指针指向了一个具有8个int类型元素的数组。下面给出一个数组指针的示例。阿萨
去的是是实时啊啊

数组的内存布局

int a[5];

sizeof(a)的值为 sizeof(int)*5,32 位系统下为 20。

  • 单独访问a,是数组的首地址

  • a[0]是首元素的首地址。

  • 然&a[0]和&a的值一样,但其意义不一样。

数组名 a 作为左值和右值的区别

  • a 作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址
  • a 不能作为左值。

&a[0]和&a 的区别

虽然&a[0]和&a的值一样,但其意义不一样。
在这里插入图片描述
从运行结果可以清楚地知道,&arr[0]和arr 所占用的内存大小并不相同,&arr[0]代表一个地址变量,由于在32位计算机中地址变量是由4字节大小来表示的,因此&arr[0]的大小为4字节,而arr的大小却是13字节,这是因为它代表的是整个数组,在采用字符串常量进行初始化时会在字符串常量的后面添加一个串结束符‘0’,所以相当于定义一个数组长度为13的字符数组char arr[13],进而可以将arr视为int [13]这种特殊的类型,所以它占用内存是13字节的大小。

指针

野指针

未初始化的指针就是野指针
1.野指针是指向不可用内存的指针,当指针被创建时,没有初始化,指针不可能自动指向NULL,这时默认值是随机的,此时的指针成为野指针。

2.当指针被free或delete释放掉时,如果没有把指针设置为NULL,则会产生野指针,因为释放掉的仅仅是指针指向的内存,并没有把指针本身释放掉。

3.第三个造成野指针的原因是指针操作超越了变量的作用范围。

如何避免野指针?

1.对指针进行初始化。

//将指针初始化为NULL。
char *   p  = NULL;
//用malloc分配内存
char * p = (char * )malloc(sizeof(char));
//用已有合法的可访问的内存地址对指针初始化
char num[ 30] = {0};
char *p = num;

2.指针用完后释放内存,将指针赋NULL。

delete(p);
p = NULL;

注: malloc函数分配完内存后需注意:
a.检查是否分配成功(若分配成功,返回内存的首地址;分配不成功,返回NULL。可以通过if语句来判断)
b.清空内存中的数据(malloc分配的空间里可能存在垃圾值,用memset或bzero函数清空内存)

//s是 需要置零的空间的起始地址; n是 要置零的数据字节个数。
void bzero(void *s, int n);
// 如果要清空空间的首地址为p,value为值,size为字节数。
void memset(void *start, int value, int size);

C/CPP常用库函数

strlen 求数组长度,不包括申请数组默认的/0结束符。

strcpy

字符串拷贝函数
注意拷贝的字符串需要小于待拷贝的数组字符串
在这里插入图片描述

strlen

求字符串长度

移位操作

不要直接给寄存器赋目标值是因为你只知道要把目标位设置为某值,但是其他的位你并不知道原本是多少,所以要先读取这个寄存器的整体值,然后再修改其中的目标位,然后再把修改后的值写到寄存器。
& 、| 、^ 、~ 、<<、>>
某位置一清0;
Y&=~(1<<x)
Y|=(1<<x)

连续两位
Y&=~(3<<x)
Y|=(3<<x)

大小端

  • 对于16位宽的数0x1234,其中高字节指的是高8位,即前两位。所以,高字节是0x12。
  • 数据16位宽的数0x1234代表什么数据16位宽的数0x1234代表一个十进制数4660。
    在这里插入图片描述
static uint32_t m=0x87654321;
char *p=(char*)&m;
void test(void)
{   
    printf("P  :0x%p: %x\n",p,*p);
    printf("P+1:0x%p: %x\n",p+1,*(p+1));
    printf("P+2:0x%p: %x\n",p+2,*(p+2));
    printf("P+3:0x%p: %x\n",p+3,*(p+3));
}

陷阱易错

类型转换

1.隐式转换
隐式转换又称为自动转换,这种转换会在计算时自动将表达式中的常量和变量转换成正确的类型,以确保计算结果的正确,而转换的方式则遵循由低类型向高类型的转换。 在这里插入图片描述
分析上面的隐式转换,当表达式中出现多种类型时要格外注意,以免出现结果与期望的不符,所以建议在混有多种类型的表达式中采用显式转换。

在这里插入图片描述

短路求值

#include <stdio.h>
int main()
{
   int i = 6;
   int j = 1;
   if(i>0||(j++)>0);
   printf("%D\r\n",j);
   return 0;
}

输出为什么不是2,而是1呢?其实,这里就涉及一个短路计算的问题。由于i语句是个条件判断语句,里面是有两个简单语句进行或运算组合的复合语句,因为或运算中,只要参与或运算的两个表达式的值都为真,则整个运算结果为真,而由于变量i的值为6,已经大于0了,而该语句已经为true,则不需要执行后续的j+操作来判断真假,所以后续的j++操作不需要执行,j的值仍然为1。
因为短路计算的问题,对于&&操作,由于在两个表达式的返回值中,如果有一个为假则整个表达式的值都为假,如果前一个语句的返回值为false,则无论后一个语句的返回值是真是假,整个条件判断都为假,不用执行后一个语句,而a>b的返回值为false达式n=c>d,所以,n的值保持为初值2。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值