C语言条件预处理命令
1 /* 2 格式: 3 #ifdef 标识符 4 程序1 5 #else 6 程序2 7 #endif 8 标识符已经定义时,程序段1才参加编译 9 应用:如调试版本,发行版本,便于调试 10 11 */ 12 #include
13 int main() 14 { 15 #ifdef ABC // #define ABC 1 16 printf("程序一"); 17 #endif 18 printf("hello world\n"); 19 return 0; 20 }
类型修饰符register (不常用)
register int a;
内存(存储器), 寄存器
限制变量定义在寄存器上的修饰符,定义一些快速访问的变量,编译器会尽量的安排CPU的寄存器去存放在这个a。如果寄存器不足时,a还是放在存储器中。
类型修饰符volatite
告知编译器编译方法的关键字,不优化编译
修饰变量的值的修改,不仅仅可以通过软件,也可以通过其他方式(硬件外部的用户)
类型修饰符const
常量的定义,只读的变量,参考收藏的文章
指针+const
//内存属性 // 1、内存操作的大小 // 2、内存的变化性,可写可读 const char *p; //常用这个 字符串 “hello world ” "aaa" char const *p; //p指向的内容可读不可写 char *const p; // 硬件资源 LCD 固定地址内容可变 char *p const; const char *const p; //ROM 什么都不变
总结:const在char的左边那就是不能修改地址中的值,在右边就是不能指向其他地址,左右都有那就是既不能修改地址的值也不能指向其他地址。
动态内存管理
malloc:
函数原型:void *malloc(size_t size); 功能:malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的指针 返回值:成功 void指针(void *) 失败 NULL
函数原型: void free(void *ptr) 功能:释放ptr参数指向的内存空间。该空间必须是由malloc、calloc或realloc函数申请的 返回值:无
内存泄漏:
#include#include int main(void) { while(1) { malloc(1024); } return 0; } 结果:卡顿
导致内存泄漏主要有两种情况:
- 隐式内存泄漏(即用完内存块没有及时使用free函数释放)
- 丢失内存块地址
以mem开头的函数包含在string.h头文件中:如memset,memcpy
void *memset(void *s,int c,size_t n) 总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c
void *memset(void *s,int c,size_t n) 总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c
#include#include #include<string.h> #define N 10 int main(void) { int *ptr=NULL; int i; ptr=(int *)malloc(N*sizeof(int)); if(ptr==NULL) { exit(1); } memset(ptr,0,N*sizeof(int)); for(i=0;i ) { printf("%d",ptr[i]); } putchar('\n'); free(ptr); return 0; }
另外还有calloc:申请并出示化一系列内存空间 realloc:重新分配空间
结构体:可以将多种数据类型组合起来的结构
声明方式:
1 struct 结构体名称{ 2 类型 变量名; 3 }结构变量1, 结构变量2;
结构体的大小:
struct st01{ char c; }; sizeof(struct st01);//c++ 直接st01就行 //输出结果为1
struct st1{ char c1; char c2; int a; }; struct st2{ char c1; int al char c2; }; sizeof(struct st1);//8 sizeof(struct st2);//12
struct st3{ char arr[5]; int b; short c; }; struct st4{ char arr[5]; short c; int b; }; sizeof(struct st3);//16 sizeof(struct st4);//12
struct st5{ char c1; char c2; double a; }; struct st6{ char c1; double a; char c2; }; sizeof(struct st5);//16 sizeof(struct st6);//24
结论:
- 结构体定义的时候,变量成员的顺序会影响结构体的大小
- 对齐:成员变量以什么样的方式排列,紧密排列还是松散,中间是不是有间隔。
- 大小影响因素:成员变量的大小;对齐方式
- 编译器对于对齐方式,可以选择的,VS默认是8字节对齐的。字节对齐:1,4,8,16;不同类型变量的对齐
- 如果编译器的对齐方式大于4字节:int:4字节,必须以字节的倍数分配地址;如果结构体里面有int,那么结构体的大小就是4的整数倍。
- 如果short,double,同样的道理;
- 如果有相同类型的变量呢,一定要放在一起,会减少结构体的空间。
- 结构体定义,变量类型从小到大的顺序比较合适(建议)
- 程序中设置内存对齐#pragma pack(n) 如:#pragma pack(1) //1个字节对齐,会紧密排列
关键字union
union 关键字的用法与struct 的用法非常类似。
union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在union 中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。
这里需要考虑存储模式:大端模式和小端模式。
- 大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
- 小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
枚举类型enum
以下代码定义了这种新的数据类型 - 枚举型
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
(1) 枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。
(2) DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
(3) 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
(4) 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
(5) 枚举型是预处理指令#define的替代。
(6) 类型定义以分号;结束。
指针
指针是一个特殊的变量,它是存放地址的。
//*:取值操做符 //&:取址操做符 &与*优先级相同 int i=2000; int *pointer; pointer=&i; printf("%d\n",*pointer);
指针变量和指针:
知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此,又把变量的地址称为该变量的“指针”
C语言可以定义一类特殊的变量,这些变量专门用来存放变量的地址,称为指针变量。
注意:指针变量的值(即指针变量中存放的值)是地址(即指针)。
float *pointer_1;//指针变量名是pointer_1,而不是*pointer_1
几种常用格式:
pointer=&a;
&*pointer //与&a相同,即变量a的地址】 *&a //先&a,得到a的地址,再进行*运算。即&a所指向的变量,也就是变量a (*pointer)++ //相当于a++
1 #include2 void main() 3 { 4 int *p1,*p2,*p,a,b; 5 scanf("%d %d",&a,&b); 6 p1=&a; 7 p2=&b; 8 if(a<b) 9 { 10 p=p1; 11 p1=p2; 12 p2=p; 13 } 14 printf("a=%d,b=%d\n",a,b); 15 printf("max=%d,min=%d\n",*p1,*p2); 16 17 }
多级指针:存放地址的地址空间,更多的是描述线性关系。
char **p;(圆圈p) p[m]=NULL结束
指针数组
char *a[100] sizeof(a)=100*4; //一个指针4个地址
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。、
具体可以参考收藏的文章。
数组越界
所谓的数组越界,简单地讲就是指数组下标变量的取值超过了初始定义时的大小,导致对数组元素的访问出现在数组的范围之外,这类错误也是 C 语言程序中最常见的错误之一。
C 语言并不检验数组边界,数组的两端都有可能越界,从而使其他变量的数据甚至程序代码被破坏。
1 void test1() 2 { 3 char string[10]; 4 char* str1="0123456789"; 5 strcpy(string, str1); 6 }
string数组越界,因为字符串长度为10,还有一个结束符‘\0’。所以总共有11个字符长度。string数组大小为10,这里越界了。
使用strcpy函数的时候一定要注意前面目的数组的大小一定要大于后面字符串的大小,否则便是访问越界。
void test2() { char string[10], str1[10]; for(i=0; i<10;i++) { str1[i] =’a’; } strcpy(string, str1); }
这里有一个一眼就能看出的问题,那就是变量i没有定义,这在代码编译阶段编译器可以帮你发现,很容易搞定。然而很多问题是自己造成的漏洞,编译器是帮不上什么忙的。这里最大的问题还是str1没有结束符,因为strcpy的第二个参数应该是一个字符串常量。该函数就是利用判断第二个参数的结束符来得到是否拷贝完毕。所以在for循环后面应加上str1p[9] = ‘\0’;
字符数组和字符串的最明显的区别就是字符串会被默认的加上结束符‘\0’。
void test3(char* str1) { char string[10]; if(strlen(str1)<=10) { strcpy(string, str1); } } //strlen函数得到字符串除结束符外的长度。如果这里是<=10话,就很明显越界了。