目录:
13、浮点数与整数比较大小
14、数组
1)定义一个空数组 2)不定长数组(realloc见本章第15)
15、realloc更改己经配置内存空间
16、堆、栈、存储区
1)C/C++内存的4个区 2)堆和栈中的存储内容
17、#pragma pack
18、typedef和#define的用法与区别
下续:C语言使用相关汇总3。
13、浮点数与整数比较大小
C语言中浮点数不能直接和整数比较大小,一般方法如下。
1)首先将整数转换为浮点数可以用强制类型转换或赋值给double变量;其次两浮点数相减最后差值取绝对值,检查差值与要求精度差距。
#include "math.h"
int equ_double_int(double a, int c)
{
if(fabs(a-c) < 0.00005)
return(1);
else
return(0);
}
2)将整数乘以1.0,比如int a; a*1.0编译器认为就是一个浮点数。
3)类型强制转换。
4)float x=10.001;int a=10;
if(floor(x+0.0005)>a)
floor函数用于返回 ≤ 浮点参数值的最大整数。
14、数组
1)定义一个空数组
输出3,6,9,12......
#include"stdio.h"
void main(void)
{
int array[100];//空数组的定义
int i;
//循环赋值
for(i=0; i<100; i++)
array[i] = (i + 1) * 3;
//输出
for(i=0; i<100; i+=)
printf("array[%d] = %d\n", i, array[i]);
}
2)不定长数组(realloc见本章第15)
手里有一小段MATLAB程序需要转化成C语言。
MATLAB里输入的矩阵可以是任意大小的,但是C语言里的数组一定要是固定大小,对于大小不能确定的数组我想到了用malloc动态申请内存,但是就算用malloc也必须是申请一定大小的存储空间(比如键盘输入的).....
但是我的MATLAB里的向量的长度是根据以前的数据算出来的,在MATLAB里非常容易得到向量长度,一句length(a)就知道向量长度了,MATLAB里不知道长度的向量怎样转化成C语言里的数组并且可以求得其元素个数?
假设数组存的是int型,那么你先申请10个元素
int* a = (int*)malloc(sizeof(int)*10);
如果又来了一个元素,那么你就可以
a=(int *)realloc(a,11*sizeof(int)); //更改已经配置的内存空间,动态分配地址然后不够再追加
求元素个数:
int i,n=1;
for(i = 0;(a+i)!=NULL;i++)
;
n=i+1; //n就是元素个数
如果你定义的是int型数组,比如 int a[10];它的长度就更简单了,n = sizeof(a)/sizeof(int)
用指针,如:
long *UserTime = (long *)malloc(size); //size是你动态需要的大小
然后就可以:
memset(UserTime,0,size); //初始化为0
UserTime[0] = xxx; //象数组那样使用
UserTime++; //等于数组下标加一
UserTime += xxx; //等于下标加xxx
free(UserTime); //用完释放
C++程序
#include "stdio.h"
#include "malloc.h"
int main()
{
printf("输入数组大小:\n");
int size;
scanf("%d",&size);
int i;
char *array = (char*)malloc(size * sizeof(int));
for(i=0;i
{
printf("请输入数组元素!\n");
scanf("%d",&array[i]);
printf("array[%d]=%d\n",i,array[i]);
}
free(array);//malloc申请的内存free来释放,new申请的内存用delete释放
return 0;
}
15、realloc更改己经配置内存空间
realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。
如果将分配的内存减少,realloc仅仅是改变索引的信息。
如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆(由系统分配)中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的
指针有可能和原来的指针一样,即不能再次释放掉原来的指针。
看一下示例代码:
#include "stdio.h"
#include "stdlib.h"
int main(int argc, char* argv[], char* envp[])
{
int ValueInput;
int n;
int *numbers1;
int *numbers2;
numbers1=NULL;
if((numbers2=(int *)malloc(5*sizeof(int)))==NULL)//为numbers2在堆中分配内存空间
{
printf("malloc memory unsuccessful");
exit(1);
}
printf("numbers2 addr: %8X\n",(int)numbers2);
for(n=0;n<5;n++) //初始化
{
*(numbers2+n)=n;
//printf("numbers2's data: %d\n",*(numbers2+n));
}
printf("Enter new size: ");
scanf("%d",&ValueInput);
//重新分配内存空间,如果分配成功的话,就释放numbers2指针, 但是并没有将numbers2指针赋为NULL,也就是说释放掉的是系统分配的堆空间,
//和该指针没有直接的关系,现在仍然可以用numbers2来访问这部分堆空间,但是现在的堆空间已经不属于该进程的了。
numbers1=(int *)realloc(numbers2,(ValueInput+5)*sizeof(int));
if(numbers1==NULL)
{
printf("Error (re)allocating memory");
exit(1);
}
printf("numbers1 addr: %8X\n",(int)numbers1);
for(n=0;n<<span style="font-family: 宋体; font-size: 12px;">ValueInput;n++)//新数据初始化
{
*(numbers1+5+n)=n+5;
//printf("numbers1' new data: %d\n",*(numbers1+5+n));
}
printf("\n");
free(numbers1);//释放numbers1,此处不需要释放numbers1,因为在realloc()时已经释放
numbers1=NULL;
//free(numbers2);//不能再次释放
return 0;
}
如果当前内存段后有足够的空间,realloc()返回原来的指针:
1. yugsuo@ubuntu:~/linux/memange$ gcc -g -o realloc realloc_example.c
2. yugsuo@ubuntu:~/linux/memange$ ./realloc
3. numbers2 addr: 8AFC008
4. Enter new size: 10
5. numbers1 addr: 8AFC008
如果当前内存段后没有足够的空间,realloc()返回一个新的内存段的指针:
1. yugsuo@ubuntu:~/linux/memange$ ./realloc
2. numbers2 addr: 9505008
3. Enter new size: 1000000
4. numbers1 addr: B716F008
16、堆、栈、存储区
1)C/C++内存的4个区
在C/C++中,内存分成4个区,他们分别是堆、栈、静态存储区和常量存储区。
栈:就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量,函数参数等。栈就象车站一样,只是一个临时场所。
在函数体中定义的变量通常是在栈上,m是局部变量:在栈中分配,在函数func被调用时才被创建,生命周期为函数func内。m每次调用func都会创建,函数结束就销毁。
堆:又叫自由存储区,它是在程序执行的过程中动态分配的,它最大的特性就是动态性。由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制(程序员分配),一般一个malloc就要对应一个free。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。如果分配了堆对象,却忘记了释放,就会产生内存泄漏。而如果已释放了对象,却没有将相应的指针置为NULL,该指针就是“悬挂指针”。
用malloc, calloc, realloc(详见本章15、realloc更改己经配置内存空间指令)等分配内存的函数分配得到的就是在堆上。
静态存储区:所有的静态变量、全局变量都于静态存储区分配。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束释放。
在所有函数体外定义的是全局变量,加了static修饰符后不管在哪里都存放在静态存储区,在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。n是全局变量:储存在静态存储区,进入main函数之前就被创建,生命周期为整个源程序。n只创建一次。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然你要通过非正当手段也可以修改,而且方法很多)常量字符串都存放在常量存储区,返回的是常量字符串的首地址。另外函数中的“12345”这样的字符串存放在常量存储区。
char *s中的s是指针,而指针是指向一块内存区域,它指向的内存区域的大小可以随时改变,而且当指针指向常量字符串时,它的内容是不可以被修改的,否则在运行时会报错。 char s[]中的s是数组首地址,而数组首地址对应着一块内存区域,其地址和容量在生命期里不会改变,只有数组的内容可以改变。
2)堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
示例:
int a = 0; //全局初始化区
char *p1; //全局未初始化区
void main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "12345"; //12345在常量存储区,p3在栈上
static int c = 0; //静态存储区初始化区
p1 = (char *)malloc(10); //分配得来10字节的区域 存在堆区
p2 = (char *)malloc(20); //分配得来20字节的区域 存在堆区
strcpy(p1, "12345");
//12345放在常量存储区,编译器可能会将它与p3所指向的"12345"优化成一块
}
17、#pragma pack
有关内容:C语言-结构体struct。
在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。
下面以TCP协议首部为例,说明如何定义协议结构。其协议结构定义如下:
#pragma pack(1) //按照1字节方式进行对齐 设置结构体的边界对齐为1个字节,也就是所有数据在内存中是连续存储的。
struct TCPHEADER
{
shortSrcPort; //16位源端口号
shortDstPort; //16位目的端口号
intSerialNo; //32位序列号
intAckNo; //32位确认号
unsignedcharHaderLen:4; //4位首部长度
unsignedcharReserved1:4; //保留16位中的4位
unsignedcharReserved2:2; //保留16位中的2位
unsignedcharURG:1;
unsignedcharACK:1;
unsignedcharPSH:1;
unsignedcharRST:1;
unsignedcharSYN:1;
unsignedcharFIN:1;
shortWindowSize; //16位窗口大小
shortTcpChkSum; //16位TCP检验和
shortUrgentPointer; //16位紧急指针
};
#pragm apop() //取消1字节对齐方式
#pragma pack规定的对齐长度,实际使用的规则是: 结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
但是,当#pragma pack的值等于或超过最长数据成员的长度的时候,这个值的大小将不产生任何效果。 而结构整体的对齐,则按照结构体中最大的数据成员进行。
18、typedef和#define的用法与区别
1)#define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不管含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。例如:
#define PI 3.1415926
程序中的:area=PI*r*r 会替换为3.1415926*r*r
如果你把#define语句中的数字9 写成字母g 预处理也照样带入。
比如4个芯片的地址定义
#define Chip1 (unsigned int) 0<<14
#define Chip2 (unsigned int) 2<<14
#define Chip3 (unsigned int) 1<<14
#define Chip4 (unsigned int) 3<<14 //unsigned int共16位二进制,将3D=0011B左移14位
4个芯片地址
Chip1: 0x0
Chip2: 0x8000
Chip3: 0x4000
Chip4: 0xc000
--------------------------------------
2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,But you cannot use the typedef specifier inside a function definition。
--------------------------------------
3)typedef int * int_ptr;与#define int_ptr int *
作用都是用int_ptr代表 int * ,但是二者不同。正如前面所说,#define在预处理时进行简单的替换,而typedef不是简单替换,而是采用如同定义变量的方法那样来声明一种类型。也就是说;
#define int_ptr int *
int_ptr a, b; //相当于int * a, b; 只是简单的宏替换
typedef int* int_ptr;
int_ptr a, b; //a, b 都为指向int的指针,typedef为int* 引入了一个新的助记符
这也说明了为什么下面观点成立:
typedef (int *) pint ; | const pint p ;//p不可更改,但p指向的内容可更改 | pint是一种指针类型const pint p 就是把指针给锁住了,p不可更改 |
#define PINT (int *) | const PINT p ;//p可更改,但是p指向的内容不可更改。 | const PINT p 是const int * p 锁的是指针p所指的对象。 |
--------------------------------------
4) #define不是语句,不要在行末加分号,否则会连分号一块置换。typedef后面要加分号。
青春时代是一个短暂的美梦,当你醒来时,它早已消失得无影无踪。觉得不错,动动发财的小手点个赞哦!