第十章
结构
typedef struct {
int a;
short b[2];
}Ex2;
typedef struct EX {
int a;
char b[3];
Ex2 c;
struct EX *d;
}Ex;
EX的存储结构图如下:
-
结构的存储分配
struct ALIGN {
char a;
int b;
char c;
};
-
系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结构的其实存储位置必须是结构中边界要求最严格的数据类型所要求的位置。因此,成员a必须存储于一个能够被4整除的地址
-
如果声明了相同类型的第2个变,它的其实存储位置也必须满足4这个边界,所以上方每个结构将占据12个字节的内存空间,但只使用其中的6个
-
如果有必要,可以让那些对边界要求最严格的成员首先出现,对边界要求最弱的成员最后出现(比如程序将创建几百个甚至上千个结构)
例如:
struct ALIGN2 {
int b;
char a;
char c;
};
上方占据了8个字节的空间,只有结构最后面需要调过的两个字节才被浪费
sizeof操作个能够得出哟个结构的整体长度,保安阔因边界对其而跳过的那些字节。
如果必须确定结构中某个成员的实际位置,因考虑边界对齐因素,使用offsetof宏(定义于stddef.h)
原型:offsetof(type,member);
offsetof(struct ALIGN,b);//返回4
-
作为函数参数的结构
传递给函数的是一个指向结构的指针,指针比整个结构要小得多,所以把它压在堆栈上效率会提高很多,(有时把参数声明为寄存器变量也可提高效率)
typedef struct {
char product[PRODUCT_SIZE];
int quantity;
float unit_price;
float total_amount;
} Transaction;
//参数传递整个结构
void print_receipt(Transaction trans){
printf("&s\n",trans.product);
printf("%d @ %.2f total %.2f\n",trans.quantity,
trans.unit_price,trans.total_amount);
}
//参数传递一个指向结构的指针
void printf_receipt(Transaction *trans){
printf("&s\n",trans->product);
printf("%d @ %.2f total %.2f\n",trans->quantity,
trans->unit_price,trans->total_amount);
}
联合
联合的所有成员引用的是内存中的相同位置,如果想在不同的时刻把不同的东西存储于同一个位置,就可以使用联合
union{
/*
在一个浮点型和整型都是32为的机器上,变量fi只占据内存中一个32为的字
*/
float f;
int i;
}fi;
int main(){
//首先把3.14159的浮点表示形式存储于fi,然后把这些相同的位当作一个整型值打印输出
fi.f = 3.14159;
printf("%d\n",fi.i);
}
位段
第十一章
动态内存分配
void *malloc(size_t size);
void free(void *pointer);
void *calloc(size_t num_elemrnts,size_t element_size);
void realloc(void *ptr,size_t new_size);
malloc和calloc都用于分配内存,calloc返回指向内存的指针之前把它初始化为0,calloc根据参数提供的所需元素的数量和每个元素是的字节数计算出需要分配的内存,分配失败,返回NULL。
realloc函数用于修改一个原先已经分配的内存块的大小,可以是一块内存扩大或缩小
#include <stdio.h>
#include <stdlib.h>
int main(){
int n;
printf("enter size of array");
scanf("%d",&n);
//int a[n];
int *a = (int*)malloc(n*sizeof(int));//如果不初始化,随机值
int *a = (int*)calloc(n,sizeof(int));//如果不初始化,值为0
for(int i=0;i<n;i++){
a[i] = i+1;
}
free(a);
/*
释放之后还是可以访问那块内存,就是说如果你知道地址,你是可以看到那个地址中的值的
这也是危险的,你无法知道你读写的地址上会是什么,不知道他的行为
a[2] = 6;//虽然可以修改地址中的值,但在其他机器上程序可能崩溃
*/
int *b = (int*)realloc(a,2*n*sizeof(int));//扩大2倍空间
int *a = (int*)realloc(a,0);//释放a 等于 free(a);
int *b = (int*)realloc(NULL,*n*sizeof(int)); //等于 (int*)malloc(n*sizeof(int));
for(int i = 0;i<n;i++){
printf("%d",b[i]);
}
}
第十三章
高级指针话题
-
指向指针的指针
int i;
int *pi;
int **ppi;
ppi = π
*ppi = &i;
函数所拥有的只是一个只想需要修改的内存位置的指针,所以要对该指针进行间接访问操作以访问需要的修改的变量
-
高级声明
声明 | 对于声明的解释 |
---|---|
int f(); | f是一个函数,返回值为int型的函数 |
int *f(); | f是一个函数,返回值类型是一个指向int型的指针 |
int (*f)(); | f是一个函数指针,它所指向的函数返回值是int型 |
int *( *f)(); | f是一个函数指针,它所指向的函数返回值是一个int型指针 |
int *f[] | f是一个数组,它的元素类型是指向int型的指针 |
int f()[] | 非法,函数只能返回标量值,不能返回数组 |
int f[] () | 非法,数字元素必须具有相同的长度,但不同的函数显然可能具有不同的长度 |
int (*f[])() | f是一个数组,数组元素的类型是函数指针 |
int *( *f[])() | f是一个指针数组,指针指向的类型是返回值为int型指针的函数 |
-
函数指针
程序中每个函数都位于内存中的某个位置,,所以存在指向那个位置的指针是完全可能的
a. int abc();
b. int abc[3];
c. int**abc();
d. int (*abc)();
e. int(*abc)[6];
f. int *abc();
g. int **(*abc[6]);
h. int **abc[6];
i. int *(*abc)[6];
j. int *(*abc())();
k. int(**(*abc)())();
l. int (*(*abc)())[6];
m. int *(*(*(*abc)())[6])();
解释 | |
---|---|
a | 返回值为int的函数 |
b | int型数组 |
c | 返回值为"int型指针的指针"的函数 |
d | 返回值为int的函数指针 |
e | 指向"int型数组"的指针 |
f | 返回值为"int型指针"的函数 |
g | 指向"返回自豪为int型指针的指针的函数"的指针的数组 |
h | int型指针的指针数组 |
i | 指向"int型指针数组"的指针 |
j | 返回值为"返回值为int型指针的函数指针"的函数 |
k | 返回值为"返回值为int的函数指针的指针"的函数指针 |
l | 返回值为"指向int型数组的指针"的函数指针 |
m | 返回值为"指向'返回值为int型指针的函数指针'的数组的指针"的函数指针 |
-
命令行参数
C程序的main函数具有两个形参第一个ming通常为argc,它表示命令行参数的数目,第二个通常为argv,它指向一组参数值
int main(int argc,char **argv){}
注意指针数组:这个数组的每个元素都是一个字符指针,数组的末尾是一个NULL指针,argc的值和这个NULL值都用于确定实际传递了多少个参数,argv指向数组的第一个元素,这就是它被声明为一个指向字符的指针的指针的原因
-
字符串常量
当一个字符串常量出现于表达式中时,它的值是个指针常量
"xyz"+1//y
*"xyz"//x
"xyz"[2]//z
*("xyz"+4)//不可预测的字符
第十四章
预处理器
-
宏
#define机制包括一个规定,允许把参数替换到文本中这种实现通常称为宏或定义宏
#define name(parameter-list) stuff
//这里加括号是为了防止一些调用错误发生
#define SQUARE(x) ((x)*(x))
-
宏与函数
要想区别宏和函数,要履行命名约定,例:
value = max(a,b);
value = MAX(a,b);
#define MALLOC(n,type)\
((type*)malloc((n) * sizeof(type)))
pi = MALLOC(25,int);
//上述例子将被替换为
pi = ((int*)malloc((25)* sizeof(int)));
宏与函数的区别
属性 | #define宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都被插入到了程序中除了非常小的宏,程序的长度将大幅增加 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用/返回的额外开销 |
操作符优先级 | 宏参数的求值实在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果 | 函数参数只在函数调用时求值一次,它的结果值传递给函数,表达式的求值结果更容易预测 |
参数求值 | 参数每次用于宏定义时,他们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预料的结果 | 参数在函数被调用前只求值一次,在函数中多次使用参数并不会导致多个求值过程,参数的副作用并不会造成任何特殊的问题 |
参数类型 | 宏与类型无关,只要参数的操作时合法的,它可以使用于任何参数类型 | 函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使他们执行的任务是相同的 |
-
#undef
#undef name;
-
条件编译
//constant-expression(常量表达式)由预处理器进行求值,如果它们的值是非零值(真),那么statements部分就会被正常编译,否则预处理器就静默的删除它们
//if 或 ifdef
#if constant-expression
statements
#elif constant-expression
other statements ...
#else
other statements
#endif
-
文件包含
函数库文件包含
#include < filename >
本地文件包含
如果头文件并未找到,就像查找函数库头文件一样在标准位置查找本地头文件
#include "filename"