防止被编译器优化,修饰变量
中断服务程序中修改的供其他程序检测的变量需要加volatile;
寄存器类型的变量
多次读写的变量
多任务环境下各任务间共享的标志应该加volatile;
这个关键字一般不使用,在代码后期优化的时候使用
修饰变量:
修饰函数:
static是一个很有用的关键字,使用得当可以使程序锦上添花。当然,有的公司编码规范明确规定只用于本文件的函数要全部使用static关键字声明,这是一个良好的编码风格。
对于所有的对象〈不仅仅是静态对象),初始化都只有一次,而由于静态变量具有"记忆"功能,初始化后,一直都没有被销毁,五段三区都会保存在内存区域的静态变量存储区中,所以不会再次初始化。存放在静态区的变量的生命周期一般比较长,它与整个程序"同生死、共存亡",所以它只需初始化一次。而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁。
代码优化阶段
修饰对象成只读,防止误修改,内存只读存储在符号表里面成为常量
在讲解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在函数声明时修饰参数
作为参数
作为返回值
const char GetString() //定义一个函数
char *str= GetString() //错误,因为str没有被 const修饰
const char *str=GetString() //正确
const 数组
常量数组
3.const作为全局变量
C语言一切变量或者函数具有两个存储类型与type数据类型的属性
补充static
C++中new、delete。new动态申请内存,创建一个对象,执行构造函数对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。delete 会调用对象的析构函数
new 对应 free 只会释放内存,new 调用构造函数。malloc与 free 是 C++/C 语言的标准库函数new/delete 是 C++的运算符。
mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。
malloc() 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。如果希望在分配内存的同时进行初始化,请使用 calloc() 函数。【返回值】分配成功返回指向该内存的地址,失败则返回 NULL。
new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。
编程实例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;
}
}
}
}
int p=10,c; c = p++;问当前c值10
宏定义为指针
#include
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;
}
#define SECOND_PER_YEAR (60*60*24*365)UL
不可行,如果在头文件中定义静态变量,会造成资源浪费的问题,同时也可能引起程序错误。因为如果在使用了该头文件的每个C语言文件中定义静态变量,按照编译的步骤,在每个头文件中都会单独存在一个静态变量,从而会引起空间浪费或者程序错误所以,不推荐在头文件中定义任何变量,当然也包括静态变量。
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
#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语言参数入栈顺序的好处就是可以动态变化参数个数。自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。
栈:在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数。在大多数c编译器中,参数是由右往左压栈的,然后是函数中的局部变量。静态变量是不入栈的。当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,,也就是主函数的下一条指令,程序由该点继续执行。
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。
它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
源程序是指未经编译的,按照一定的程序设计语言规范书写的,人类可读的文本文件,源程序就是所写好的代码。可执行程序,即常说的.exe程序,可以执行程序,完成计算机功能。在C语言中,.c文件就是所谓的源文件。源程序到可执行程序的过程。在这个过程中,会发生如下的变化:
.c文件生成.obj文件的过程,称为编译,.obj文件生成到.exe文件的过程,称为链接。
.obj文件就是一个是程序编译生成的二进制文件,当.exe文件生成以后.obj文件就会被删除。事实上,.c文件生成.exe文件的过程总共是经历了预处理,编译,汇编,链接,这四个过程。
添加链接描述
形参与实参的区别
需要注意的是传值和传址的区别:
int sort(int a[],int n);
int main(){
int arr[5]={1,3,5,9,7};
sort(arr,5);
}
定义函数指针的方式
#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]
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[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);
strlen 求数组长度,不包括申请数组默认的/0结束符。
求字符串长度
不要直接给寄存器赋目标值是因为你只知道要把目标位设置为某值,但是其他的位你并不知道原本是多少,所以要先读取这个寄存器的整体值,然后再修改其中的目标位,然后再把修改后的值写到寄存器。 连续两位 1.隐式转换 输出为什么不是2,而是1呢?其实,这里就涉及一个短路计算的问题。由于i语句是个条件判断语句,里面是有两个简单语句进行或运算组合的复合语句,因为或运算中,只要参与或运算的两个表达式的值都为真,则整个运算结果为真,而由于变量i的值为6,已经大于0了,而该语句已经为true,则不需要执行后续的j+操作来判断真假,所以后续的j++操作不需要执行,j的值仍然为1。
& 、| 、^ 、~ 、<<、>>
某位置一清0;
Y&=~(1<
Y&=~(3<大小端
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));
}
陷阱易错
类型转换
隐式转换又称为自动转换,这种转换会在计算时自动将表达式中的常量和变量转换成正确的类型,以确保计算结果的正确,而转换的方式则遵循由低类型向高类型的转换。
分析上面的隐式转换,当表达式中出现多种类型时要格外注意,以免出现结果与期望的不符,所以建议在混有多种类型的表达式中采用显式转换。短路求值
#include
因为短路计算的问题,对于&&操作,由于在两个表达式的返回值中,如果有一个为假则整个表达式的值都为假,如果前一个语句的返回值为false,则无论后一个语句的返回值是真是假,整个条件判断都为假,不用执行后一个语句,而a>b的返回值为false达式n=c>d,所以,n的值保持为初值2。