数据结构基础——指针

一、指针的本质

1.1 指针的定义


        内存区的每一个字节有一个编号,这就是“地址”。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。

        按变量地址存取变量值的方式称为“直接访问”方式。另一种存取变量值的方式称为“间接访问”的方式。即,将变量i的地址存放在另一个变量中。

# include <stdio.h>
# include <stdlib.h>

int main()
{
    int i=10;
    int *i_pointer; //i_pointer是指针变量
    i_pointer=&i;
    
    printf("i=%d\n",i); //直接访问
    printf("*i_pointer=%d\n",*i_pointer); //间接访问
}

在C语言中,指针变量是一种特殊的变量,它是存放地址的。
定义一个指针变量 :

基类型* 指针变量名;eg. int*i_pointer;

指针的本质是“间接访问”

1.2 取地址与取值操作符

取地址操作符  & :引用 (获取一个变量的地址值)

取值操作符  * :解引用(取值,可以拿到一个地址对应位置的数据)

这两个运算符的优先级相同,但按自右而左方向结合。

注:整形变量的地址才能放到指向整型变量的指针变量中,其它同理。

1.3 指针的使用场景

(1)传递

        函数:只能传入字母a的值但没法直接修改字母a的值!

        默认情况下,参数是通过值传递的方式传递给函数的。这意味着函数内部接收到的是参数值的一个副本,对副本的任何修改都不会影响到原始的变量。

#include <stdio.h>
#include <stdlib.h>
void change(int j)
{
    j = 5;
}
void change_dizhi(int *j)
{
    *j = 5;
}

int main()
{
    int i = 10;
    printf("before change,i=%d\n", i); //i=10
    change(i); // j = i;j = 5;
    printf("after change,i=%d\n", i); //i=10 
    change_dizhi(&i);
    printf("after change_dizhi,i=%d\n", i); //i=5
    system("pause");

}

函数:只能传入字母a的值但没法直接修改字母a的值!

#include<stdio.h>
#include<stdlib.h>
void transform(int a)
{
	int num = 10;
	num = a;
	printf("num形参的值为%d\n", num);
}
void change(int a)
{
	a = 30;
}

int main()
{
	int a = 20;
	transform(a); //20
	change(a);
	printf("a的值为:%d\n",a); //20
	return 0;
}

(2)偏移

        指针即地址,就像你找到了一栋楼,该栋楼叫B栋,那么往前就是A栋,往后就是C栋,所以指针的另一个场景就是对其进行加和减,地址进行乘除是没有意义的,就像你家的地址乘5是代表什么,没有意义。工作中,我们把对指针的加减,称之为指针的偏移,加就是向后偏移,减就是向前偏移。目的是获得某地之前后的地址及其地址处的内容。

注: 1. "+1"加的是基类型的大小,即sizeof(int)、sizeof(结构体)等等。

        2. 只能偏移有基类型的,即void类型的指针不能偏移。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a[5] = { 1,2,3,4,5 };
    int* p;
    int index;
    p = a;
    for (index = 0; index < 5; index++)
    {
        printf("%d\n", *(p + index));
        // p[index]
    }
    return 0;
}
        指针与自增自减运算符

        ++、--、*、&的优先级,从右到左结合

        *p++ -> *p;p++ 

        p[0]++ ->

1.4 指针与动态内存申请

        很多人一直都非常想使用动态数组,觉的数组长度固定很不爽,其实是因为我们定义的整型,浮点型,字符型变量,数组变量,都在栈空间,栈空间的使用是在编译时确定大小,如果使用的空间大小不确定,那么我们就需要使用堆空间

malloc

语法:
void *malloc(size_t size) :

返回的是void*类型,因为不知道这段空间要用来干什么,故要自己进行类型转换

功能:函数指向一个大小为size的空间,存储空间的指针不能是栈。这样以便以后用free函数释放空间。

注:1.malloc不free,就会内存泄漏。

        2.free之后把指针要赋为NULL(free之后p中依然存有该地址值,只是该段空间不应该再访问了,别人在用了)。如果free之后不赋为NULL,拿着指针去访问对应空间(可能会拿到别人的数据,与自己预想的不同,则难以找到错误),称为野指针。

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int *p, *p1 ,*p2;
	p = (int*)malloc(4);
	*p = 1;
	p1 = (int*)malloc(4);
	*p1 = 2;
	free(p);
	p2 = (int*)malloc(4);
	*p2 = 3;
	*p = 100;
	printf("*p1=%d,*p2=%d\n", *p1, *p2); // 2 100
	system("pause");
}

1.5 堆与栈的差异

        既然都是内存空间,为什么还要分栈和堆呢?栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多(申请后free太多等)),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

注:1.堆只有我们free才会释放,否则在进程执行过程中一直有效。

        2.栈空间在函数使用完后就会释放。

#define N 1000000
#include <stdio.h>
#include<stdlib.h>
//函数栈大小是1M
char* print_stack()
{
	char c[] = "I am stack";//在print_stack()的栈空间中
	puts(c);
	return c;
}
char* print_malloc()
{
	char* p = (char*)malloc(20);
	strcpy(p, "I am malloc");
	puts(p);
	return p;
}

int main()
{
	char *p;
	p = print_stack();//该函数返回的时候空间还没被清理掉(延迟退栈)
	puts(p);//print_stack()的空间已被退掉
	p = print_malloc();
	puts(p);
	system("pause");
}

二级指针

一级指针的使用场景是传递与偏移,服务的对象是整型变量,浮点型变量,字符型变量等,那么二级指针既然是指针,其作用也是传递与偏移,服务对象更加简单,只服务于一级指针的传递与偏移

(1)传递

在子函数改变主函数某一个一级指针变量的值

#include <stdio.h>
#include<stdlib.h>

void change(int **p2, int* pj)
{
	*p2 = pj;
}
int main()
{
	int i = 10;
	int j = 5;
	int* pi = &i;
	int* pj = &j;
	printf("i=%d,j=%d,*pi=%d,*pj=%d\n", i, j, *pi, *pj);
	change(&pi, pj);
	printf("i=%d,j=%d,*pi=%d,*pj=%d\n", i, j, *pi, *pj);
	system("pause");
}

(2)偏移

一级指针的偏移服务于数组,整型一级指针服务于整型数组,所以二级指针的偏移也服务于数组,服务对象为指针数组,请看下面实例,实际在淘宝购物过程中,大家搜索的商品信息存在内存中,如果以某个查询条件搜索商品,淘宝需要把商品按你的要求进行排序,比如价格从低到高,这时交换内存中商品的信息会极大的降低效率(因为每个结构体的内存更大),因为不同用户会有不同的查询需求,每件商品本身的信息存储又较大,假如我们让每个指针指向商品信息,排序比较时,我们比较实际的商品信息,但交换的是指针,这样交换成本将会极大降低,这种思想称为索引式排序,下面的例子把字符串看成商品信息即可,将指针数组p赋值给二级指针p2,目的是为了演示二级指针的偏移,p2+1偏移的就是一个指针变量空间的大小,即sizeof(char*),对于win32控制台应用程序,就是偏移4个字节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值