抑郁症健康,内容丰富有趣,生活中的好帮手!
抑郁症健康 > C语言学习笔记10-指针(动态内存分配malloc/calloc realloc 释放free 可变数组实现

C语言学习笔记10-指针(动态内存分配malloc/calloc realloc 释放free 可变数组实现

时间:2022-11-07 06:58:29

相关推荐

C语言:指针

1.指针:保存地址的变量*p (pointer) ,这种变量的值是内存的地址。

取地址符& 只用于获取变量(有地址的东西)的地址;scanf函数-取地址符

地址的大小不一定和int型相同,取决于编译器或系统架构是x86还是x64的,有时需要强转为 long long int; 因此将地址交给整型变量是不靠谱的,我们使用指针类型。 验证方法:%#x - (int)&i %p %#p - &i %lu - sizeof

int i = 0;int p;int a[10];p = (int)&i; //强制类型转换printf("&i = %#x ", &i); //类型不匹配,自动转换printf("p = %#x\n", p);printf("real &i = %p\n", &i);printf("real &p = %p\n", &p);//void * intx86 x64printf("%lu\n", sizeof(&i)); // 4/8printf("%lu\n", sizeof(int)); // 4/4printf("%p\n", &a);printf("%p\n", a);printf("%p\n", &a[0]);printf("%p\n", &a[1]);

(补充:scanf函数传入地址时忘加&编译不报错的原因——32位编译器int和指针大小恰好一样,编译器以为传入的int是地址。)

了解一下——

(1) C语言相邻定义的变量在内存中连续排列(堆栈stack 自顶向下 高地址 - >低地址);

(2) 数组的地址:数组名就是数组首元素地址 arr &arr &arr[0] ;

(3) 一个内存空间大小1B(16进制保存),4GB内存的内存地址编号有 232 个(所以32位机器的内存封顶就是4G,只能编号这么多个);32位系统的一个内存地址编号大小是4B(32位),64位系统的内存地址编号是8B大小(64位)。

2. 指针变量的定义

定义指向: int *p = &i ; 指针p指向一个int,可以将int变量 i 的地址赋给p (不存在 int * 这种类型 – 其实C编译忽略空格)

int * p, q; 等价于 int *p, q; 均表示定义指针p和整型q (可以做 p = &q;)

int *p, *q; 这才定义了两个指针变量。

提醒:千万不要定义了指针后还没有指向变量就使用它(即不能用*p)。

3. 指针的运算

char * p = &i[0]

int * q = &j[0]

p+1和q+1分别表示在地址上移动一个sizeof(type) ,在地址层面p加了1(地址升高1),q加了4,就是让指针指向下一个变量。

所以指针如果不是指向一片连续分配的空间(如:数组; i[1] == * (p + 1)),这样的运算毫无意义。

运算 // + - += -= ++ - -

对于 * p ++ :取出p所指的数据,然后把p移向下一个位置(用于数组类的连续空间操作)。【符号优先级 ++ 高于 * 】

eg:

for(p = ai; * p != -1; ) {

printf(“%d\n”, * p++);

}

两个同类型指针相减(否则非法)的结果是这两个地址间存在几个该类型变量,而不是地址之差。任意的两个指针(不在同一个数组内的)相减和void * 这样纯粹的地址指向指针一样,不能进行减法运算。

不同类型指针不要相互赋值,用int指针给char数组操作会一次性改变4个char变量(因此也不建议强制类型转换,除非你知道自己在干嘛)

void * 俗称万能指针,纯粹访问某个地址,通常在底层编程中使用(控制寄存器或外部设备等)。【 int * p = &i; void * q = (void * ) p; 这没有改变p的变量类型,而是转变不同的眼光看待p所指的变量】

比较 // < <= == >= > !=

比较地址在内存中的大小,数组中的单元的地址一定是线性递增的。

4. 指针变量的作用

运用指针变量,我们在编程时可以通过地址访问变量。低地址不能访问,如存放ascii码的0~255等

取内容符 * (解引用)用来访问指针所指向的内存地址处的变量,可以做右值也可以做左值。注意与指针定义时的 * 区别,另外它也不是乘法运算。它与 & 互为反作用( * & q 与 & * p 【p是指针,q是普通变量,不能倒过来写,具体参考运算符优先级C语言学习笔记05】 )。

【左值:出现在赋值号左边的不是变量,而是表达式计算的值,是特殊的值(可以接收另一个值),因此称为左值;a[0] = 2; *p = 31; 。】

0地址:机器实际只有1个0地址,操作系统的进程概念会创建虚拟空间,所以每个程序都有自己的0地址。一般这是不能被访问的地址,可以使返回的指针是无效的(初始化指针为0),它被预定义为NULL(C语言中预定义NULL 0),有的编译器使用NULL和0不一定一样。【建议:一般尽量使用NULL】

int *p, q;printf("%p %p\n", p, &q);printf("q = %d\n", q); //此时p的地址若是个低地址就禁止访问,若进行访问程序可能崩溃 p = &q;q = 10;printf("%p %p\n", p, &q);printf("*p=%d q=%d\n", *p, q);printf("%d %p\n", *&q, &*p);

当指针作为函数参数时,调用函数时的参数赋值必须用实际变量的地址,在函数内用指针就能访问外部实际的变量。

void f(int *p) {printf("p = %p\n", p);printf("*p = %d\n", *p);*p = 31; //左值printf("*p' = %d\n", *p);}void g(int k) {printf("k = %d\n", k);printf("&k = %p\n", &k);k = 1;printf("k' = %d\n", k);}int main(int argc, char *argv[]){int i = 29;printf("&i = %p\n", &i);printf("i = %d\n", i);putchar(10);g(i);printf("i = %d\n", i);f(&i);g(i);return 0;}

5. 指针应用场景

(1) 交换两个变量的值。

void swap(int *a, int *b) {int t = *a;*a = *b;*b = t;}swap(&x, &y);

(2) 函数需要返回多个值,某些值就需要指针带回。

-传入函数的指针参数实际上用于保存需要带回的结果

(3) 函数返回运算状态,结果需要指针返回。

- 常用套路是让函数返回特殊的不属于有效范围内的值来表示出错:如常用-1或0(文件操作中)表示计算状态,但当任何数值都是有效的可能结果时需要分开返回状态和计算结果。

- 在C++和java中采用异常机制来解决这个问题。

6. 指针与数组、const

C语言学习笔记09-数组中说到:传入函数的数组不能在函数内计算出数组长度——因为传入的是数组首元素的地址,即函数内使用的数组和传入前外面的那个数组是同一个,说明传入函数的数组参数本质是个指针。因此,传入函数的数组参数可以写成 int a[] 也可以写成 int *a ,int * ,int [],它们作为函数原型参数都是等价的。

数组变量是特殊的指针(const的指针)!数组名和数组首元素地址一样 a == &a[0] —— int a[10]; int *p = a; //不用& 。但是数组的单元表达的是变量,需要用&取地址。因此,指针也能看成是长度为1的数组 p[0] == * p 。 int * const a -> int a[],常量指针一旦得到了某个变量的地址就不能再指向其它变量,所以两个数组间不能直接相互赋值。【具体说明:若 int * const q = &i; 此时q(一个地址)不允许被改变,q++ 是错误的,但是,*q = 26; 是可以的。】

const修饰指针( const 在 *之前还是之后 )的差异:const int * p = &i 或 int const * p = &i 表示不能用指针去修改变量 i ,但 i 可以直接修改(i = 10),指针 p 也能重新指向其它的变量(p = &j);只有 * p = 10不被允许。【使用场景:传入函数的结构体参数使用const指针,既能用较少的字节数传递值又能避免函数对外面变量的修改。

const修饰数组,表示数组的每个单元都是const int型,此时必须通过集成初始化进行赋值。同样为了保护数组传入函数后不被破坏,就可以在函数定义时这样写:int sum(const int a [], int len);

7. 动态内存分配

C99支持用读入变量来定义数组(虽然数组创建后也不能改变大小了),但在ANSI C时期程序员采用动态内存分配的方式解决类似的问题。如下: (malloc函数 头文件stdlib.h)

int * a = (int * )malloc(n * sizeof(int) ); //可申请 4 * n 个字节 malloc返回void * ,所以需要强制转换

malloc函数与calloc函数比较——

void *malloc(size_t num); malloc参数只有无符号整型的num表示分配的字节数。

void *calloc(size_t num, size_t size); 两个参数分别是无符号整型num表示分配的对象的个数,以及无符号整型size表示每个对象的大小。【比malloc更好的是calloc会初始化分配的空间,而malloc不行 , 使用如: calloc(n, sizeof(type)) 】

这时,a已经可以被当作数组使用。如:&a[i]。使用结束后记得free(a),动态分配借的要还回去。【free还的只能是借的首地址(谁开始找系统借的),如果不是程序会报错】

7.1. 查看本机剩余最大可用内存空间:(如果没有空间了,malloc申请失败返回0,也可以说是NULL)

如果上面while中的判断条件未加(),会提示以下错误:

[Warning] suggest parentheses around assignment used as truth value [-Wparentheses]

这是因为编译器生怕你在编程时搞混了赋值和比较,因此希望你明确自己要做的操作,改成图片中的样子就ok了。

7.2. 接收不定长字符串:(malloc、realloc、free)

int cnt = 0, len = 20;char ch;char *s1 = (char*)malloc(len);char *s2 = NULL; //出于安全性优化考虑,添加*s2if (s1 == NULL) {return; //直接返回被调用点}while ((ch = getchar()) != EOF) {//windows ctrl+Z linux ctrl+D 关闭程序ctrl+Cs1[cnt++] = ch; //s1[cnt] = ch;cnt++;可合并成s1[cnt++] = ch;if ( cnt >= len ) {len += 20; // *= 2s2 = (char*)realloc(s1, len);if ( s2 == NULL ) {free(s1);return;}else s1 = s2;}}s1[cnt] = '\0'; //如果不加本行,输出末尾会多出符号直至遍历到存放 0 / '\0'的内存单元puts(s1);free(s1); //切记释放内存free(s2);

动态申请内存空间后,记住合适的时机释放它,虽然一般初学编程时程序小,运行结束后操作系统会帮你善后处理,但是不释放这个习惯如果保留到较大的程序编程中就会有问题。

7.3. 实现可变数组【自定义数组结构】:(memcpy string):

#include <stdio.h>#include <stdlib.h>#include <string.h>const int BLOCK_SIZE = 20; //每20个单元一个区//定义自己的数组结构,实现可变数组//可变数组缺陷:每次需要申请更大的线性连续空间,不够高效typedef struct {int *array;int size;} Array; //此处如果改成指针类型*Array 弊端在于想要创建一个本地Array变量就做不出来了,且易造成误解Array array_create(int init_size); //创建数组void array_free(Array *a); //释放数组空间中的*arrayint array_size(const Array *a); //计算数组可用单元数int* array_at(Array *a, int index); //用于访问数组某个单元(左值、右值)int array_get(const Array *a, int index);void array_set(Array *a, int index, int value);void array_inflate(Array *a, int more_size); //可变数组核心函数:使数组变大/** ****** Main *******/int main(int argc, const char *argv[]) {int size = 20; //100Array a = array_create(size);//printf("array a's size: %d\n", array_size(&a));//*array_at(&a, 0) = 123; //array_set(&a, 0, 321);//printf("%d\n", *array_at(&a, 0)); //printf("%d\n", array_get(&a, 0));int number = 0;int cnt = 0;//输入数等于-1时表示退出while ( number != -1 ) {//scanf("%d", array_at(&a, cnt++));scanf("%d", &number);if ( number != -1 ) {*array_at(&a, cnt++) = number;}}printf("array a's size: %d\n", array_size(&a));for ( cnt -= 1; cnt >= 0; cnt-- ) {printf("%d\t", *array_at(&a, cnt));}array_free(&a);return 0;}//typedef struct {//int *array;//int size;//} Array;Array array_create(int init_size) //Array* array_create(Array *a, int init_size) a->size{Array a;a.size = init_size;a.array = (int*)malloc(sizeof(int) * a.size);//返回本地Array类型变量可以让外部的调用者更灵活地做计算,若用指针会很麻烦//如需要判断指针是否null,是否之前创建过了(需要先free)return a;}void array_free(Array *a) {free(a->array);//再加两重保险,防止外部free后再次调用a->array = NULL;a->size = 0;}//封装,隐藏内部细节,安全性高int array_size(const Array *a) {return a->size;}int* array_at(Array *a, int index) {if ( index >= a->size ) {//index < 0//array_inflate(a, index - a->size + 1); //不经济array_inflate(a, (index/BLOCK_SIZE + 1) * BLOCK_SIZE - a->size); //不一定只增加1个block}//返回指针而不是值,可以让外部调用者将其加*当做左值使用return &(a->array[index]); //优先级搞不清了,工程中通常直接加括号}//另一种方式:避免 *函数int array_get(const Array *a, int index) {return a->array[index];}void array_set(Array *a, int index, int value) //修改{a->array[index] = value;}void array_inflate(Array *a, int more_size) //理论上数组不能自增长,如何实现可变数组{//申请一块新的空间int *p = (int*)malloc(sizeof(int) * (a->size + more_size));//int i;//for ( i=0; i < a->size; i++ ) {//p[i] = a->array[i];//} //memcpy(dst, src, nbytes) //string.hmemcpy(p, a->array, sizeof(int) * a->size);free(a->array);a->array = p;a->size += more_size;}

8. 返回指针的函数 —— (返回传入的指针)

返回本地变量的地址是危险的。因为离开函数后本地变量不存在了,这个本地变量的地址可能会被重新分配给其它变量使用:

可以看到,p指向的地址变化了,有时甚至程序会崩溃!返回指针的函数,使用全局变量或静态变量相对安全(线程不安全,函数不可重用)。【注意不要使用全局变量在函数间来回传递参数和结果】

最好的做法是返回传入的指针:在主函数里建一个指针给其它函数,其他函数回传指针。

另外看一种特殊的指针 —— 函数指针

函数名也代表一个地址,指针用来存放地址,函数类型的指针可以有:

函数指针能做什么?请看:

更主要是用于

(1)接受用户输入后选择该干什么事(调用哪一个函数),可以比switch更简洁。

#include <stdio.h>void f(int i) {printf("in f(), %d\n", i);}void g(int i) {printf("in g(), %d\n", i);}void h(int i) {printf("in h(), %d\n", i);}void k(int i) {printf("in k(), %d\n", i);}/* *** */int main(){int i = 0;scanf("%d", &i);void (*fa[])(int) = {f,g,h,k}; //函数指针数组集合初始化if ( i >= 0 && i < sizeof(fa)/sizeof(fa[0]) ) {(*fa[i])(0);}//switch ( i ) {//case 0: f(0);break;//case 1: g(0);break;//case 2: h(0);break;//}//if ( i == 0 ) {//f(0);//}//else if ( i == 1 ) {//g(0);//}}

(2)向函数中传入函数:

#include <stdio.h>int plus(int a, int b) {return a + b;}int minus(int a, int b) {return a - b;}void cal(int (*f)(int, int)) {printf("%d\n", (*f)(2,3) );}/* *** */int main(){cal(plus);cal(minus);}

C语言学习笔记10-指针(动态内存分配malloc/calloc realloc 释放free 可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)

如果觉得《C语言学习笔记10-指针(动态内存分配malloc/calloc realloc 释放free 可变数组实现》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。