数组
#define N 10 int a[N]; for(int i=0;i<N;i++) { a[i] = 0; } //初始化,没有被初始化到的数组下标就被设置为0 int a[10] = {1,2,3,4,5}; //等价于 int a[10] = {1,2,3,4,5,0,0,0,0,0}; //也可以写成入如下形式相当于把整个数组全部赋值为0 int a[10]={0}; //C99中可以指定对某个元素下标赋值,剩余的就被赋值为0 int a[15] = {[2]=11, [9]=33, [14]= 99}; //对数组使用sizeof运算符 int a[10]; sizeof(a); //结果就是40 //sizeof 返回的是无符号的整形size_t,无符号和有符号之间运算会有危险, //最好使用强制转型 //数组清零 #define SIZE ( (int)(sizeof(a)/sizeof(a[0])) ) //多维数组初始化 int m[5][9] = {{1,2,3,4,5,6}, {2,3,54,5,6}, {4,5,6,7,8}, {3,5,6,7,11}}; //也可以省略外面的一层大括号 int m[5][9] : {1,2,3,4,5,6, {2,3,54,5,6, {4,5,6,7,8, {3,5,6,7,11}; //常量数组 const char hex_chars[] = {'0','1','2','3'}; //C99中也可以对多维数字进行指定下标的初始化 //C99还支持变长数组
指针
//定义一个指针 int *p; //每个指针变量只能指向一种特定的类型 int *p; double *d; char *r; //赋值和访问 &运算符是获取一个变量的地址值, *那个运算符是间接寻址 int i, *p; i = 10; p = &i; printf("%d",*p); //这样的赋值是错误的 int i, *p; i = 10; *p = &i; //这里是将 i的地址赋予p指向的内容,所以是错误的 //*p是指针指向的内容,是一个具体的值,p才表示一个地址,所以 //应该是 p = &i; 将i的地址赋予指针p才是正确的 //指针作为返回值 int *max(int *a, int *b) { if(*a > *b) { return a; } return b; } //注意,永远不要返回指向局部变量的指针 int *f(void) { int i; return &i; } //一旦函数f返回,变量i就不存在了,所以变量i的指针将是无效的,有的编译器会 //在这种情况下给出类似"function returns address of local variable"的 //警告 //未初始化的指针变量指向的是一个不确定的内存地址值 //用const保护参数,也就是指针指向的内容是常量不可改变,但指针内容本身是 //可以被改变的,也可以包含指针本身,还可以包含指针本身和指针指向的内容 //第一种情况,保护指针指向的内容,此时指针本身可以改变 int number = 100; const int *p; //*p = 0; p = &number; //第二种情况,保护指针本身,指针指向的内容可以被改变 int *const p2; *p2 = 0; //p2 = &number; //第三种情况,包含指针本身,以及指针指向的内容 const int *const p3; //*p3 = 0; //p3 = &number;
指针和数组
//定义指针,指针p指向a数组的第一个元素 int a[10], *p; p = &a[0] //设置数组a有10个元素 p = &a[2]; //指向数组a的第三个元素 int *q = p+3; //指向数组a的第六个元素,也就是a[5] p -= 4; //指向数组a的第二个元素,也就是a[1] //两个指针之间可以相减,但是两个指针必须都指向同一个数组,因为比较的是内存 //地址,如果两个数组指向的是两个不同的数组,最后计算出来的结果可能是未知的 //内存
*运算符和 ++运算符(++运算符优先级高于*)
表达式 | 含义 |
*p++或者*(p++) | 先自增p的值,然后再获得*p |
(*p)++ | 自增前的表达式是*P,以后再自增*p |
*++p或*(++p) | 先自增p,自增表达式的值是*p |
++*p或++(*p) | 先自增*p,那个自增后表达式的值是*p |
//用数组名做指针 int a[10]; *a = 7; //等于a[0] = 7; //也可以通过指针来修改a[1] *(a+1) = 12; //等于a[1] = 12; //遍历 for(p=&a[0]; p<a[N]; p++) { sum += *p; } //处理多维数组 int a[NUM_ROWS][NUM_COLS]; int *p; for(p=&a[0][0]; p<= &a[NUM_ROWS-1][NUM_COLS-1]; p++) { *p = 0; } //C99中的指针和变成数组
指针的高级应用
//内存分配函数,这些都是声明在<stdlib.h>头文件中 //malloc()函数 分配内存块,但是不对内存块进行初始化 //calloc()函数 分配内存块,并且对内存块进行初始化 //realloc()函数 调整先前分配的内存块大小 //空指针用表示 不指向任何地方的指针,用名为 NULL 的宏来表示 //NULL 定义在<stdlib.h>中 p = malloc(1000); if(NULL == p) { //分配失败 } //malloc函数原型,它返回的是void*类型指针,所以需要转型,这个void*就代表 //任何类型 void *malloc(size_t size); //分配字符串时需要分配n+1大小空间,因为字符串最后是\0结尾 p =(char*) malloc(n+1); //分配数组空间,注意分配时必须使用sizeof计算单个值占多少字节 int *a; a = malloc(n * sizeof(int)); //calloc函数原型,为nmemb个元素的数组分配内存空间,其中每个元素长度 //都是size个字节,如果要求的空间无效会返回空指针 void *calloc(size_t nmemb, size_t size); a = calloc(n, sizeof(int)); //realloc函数原型,其中ptr必须指向先前通过malloc,calloc,realloc的调用 //获得的内存块,size表示新尺寸可能会大于或小于原有尺寸。 void *realloc(void *ptr, size_t size);
C标准列出了几条关于realloc函数的规则
1.当扩展内存块时,realloc函数不会对添加进内存块的字节数进行初始化
2.如果realloc函数不能按要求扩大内存块,那么它会返回空指针,并且在原油的内存块中的数据不会发生改变
3.如果realloc函数调用时以空指针作为第一个实际参数,那么它的行为就像malloc函数一样
4.如果realloc函数被调用时以0作为第二个实际参数,那么它会释放掉内存。
注意:一旦realloc函数返回一定要对所有指针进行更新,因为ralloc函数可能会使内存块移动到了其他地方
//用free函数释放动态申请的内存,其原型为 void free(void *prt); //悬空指针问题 char *p = malloc(4); free(p); strcpy(p, "abc"); //此时p指向的内存已经被释放掉了,再对其修改可能会造成 //严重的错误 //结构体 struct node { int value; struct node *next; }; struct node *new_node= NULL; new_node = malloc(sizeof(struct node)); //写成这样是不对的,我们需要申请结构体大小的空间,而这样申请的是指针大小的 //空间,一个指针可能是4个字节,这样实际上就申请了4个字节 new_node = malloc(sizeof(new_node)); // ->运算符 (*new_node).value = 10; //等价于 node_new->value = 10; //指向指针的指针 char **ptr; *ptr = "abc"; //ptr是指向"abc"的地址 //指向函数的指针 //假设函数integrate的原型如下: (*f)说明f 是个指向函数的指针 double integrate(double (*f)(double), double a, double b); //也可以写成: double integrate(double f(double), double a, double b); //调用integrate时,将把一个函数名作为第一个实际参数,注意sin后面没有括号 //C编译器会产生指向函数的指针而不是产生函数调用的代码 result = integrate(sin, 0.0, PI/2); //在函数integrate中, *f 表示调用f 所指向的函数,*f 就是调用sin函数 // (*f)(x) 和f(x)都是一样的,但是用(*f)(x)更好,说明是f是指向函数的指针 //C函数库中的qsort函数原型 void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void*, const void*)); //base是指向数组中的第一个元素,nmemb是要排序元素的数量,size是每个 //数组元素的大小,compar是指向比较函数的指针 int compare_parts(const void *p, const void *q) { const struct part *p1 = p; const struct part *p2 = q; if(p1->number < p2->number) { return -1; } else if(p1->number == p2->number) { return 0; } return 1; } //还可以写成: int compare_parts(const void *p, const void *q) { return ((struct part*)p)->number - ((struct part*)q)->number; } //指向函数的指针,可以指向任何参数是int,返回void类型的函数 void (*pf) (int); void fun(int x); pf = fun; //让pf指向函数fun,注意fun前面不需要加 & pf(100); //受限指针 int * restrict p; //灵活数组成员 struct vstring{ int len; char chars[N]; }; //这里N表示一个字符串最大长度的宏,但是分配最大长度会导致浪费,于是采用 //如下方式申明chars长度 struct vstring{ int len; char chars[1]; }; struct vstring * str = malloc(sizeof(struct vstring) +n-1); //这样就看以达到目的了,并且内存不会浪费,这样叫 struct hack 技术 //C99支持灵活数组成员,当结构体最后一个是数组时可以省略长度 struct vstring{ int len; char chars[]; };
定义NULL为
#define NULL (void*)0;
如果把NULL 定义为0,那么把NULL赋给整型变量index是合法的,但是把NULL定义为(void*)0 则表示把指针赋值给了整型变量
将malloc函数的值类型强制转换没有任何好处,这是来自于经典C的习惯。
参考: