C语言之指针详解

指针定义

每个内存单元(一个字节)的编号(从零开始)称为地址,也叫指针,指针变量。

本质上指针就是保存地址的,可以通过地址可以找到一个内存单元

指针变量定义语法:数据类型 * 变量名,如int * pa

指针的作用: 可以通过过指针间接访问内存

在32位机器下,每个指针变量的大小就是四个字节

在64位机器下,每个指针变量的大小就是八个字节 

指针变量和普通变量的区别:

普通变量存放的是数据

指针变量存放的是地址

指针变量可以通过*操作符,操作指针变量指向的内存空间,这个过程称为解引用

int main()

{
    int a = 10;   a是整形变量,在内存中开辟四个字节的内存空间

    int* pa = &a; pa是一个指针变量,指向变量a的地址,存放a的第一个字节地址

    *pa = 10; 此处的*是解引用操作符,目的是间接访问pa的地址即a的地址,并将a的大小改成10

    return 0;

}

运行程序可知pa和a指向同一块内存,*pa即pa指向的内存地址上的值为和a相同都是10

总结1: 我们可以通过 & 符号 获取变量的地址

总结2:利用指针可以记录地址

总结3:对指针变量解引用,可以操作指针指向的内存

指针内存

指针类型决定了指针在被解引用的时候访问了几个字节

如果是int*的指针,解引用访问了四个字节

如果是char*的指针,解引用访问了一个字节

指针的类型决定了指针加减一 操作时,跳过几个字节,决定了指针的步长

空指针和野指针

空指针

空指针:指针变量指向内存中编号为0的空间

用途:初始化指针变量

注意:空指针指向的内存是不可以访问的,内存编号0 ~255为系统占用内存,不允许用户访问

如以下程序进行说明

int main()

{
    int * p = NULL; 指针变量p指向内存地址编号为0的空间,

    std::cout << *p << std::endl; 此时访问空指针报错

    system("pause");

    return 0;

}

野指针

野指针就是指针指向非法的内存空间

如下一个程序进行说明:

int main()

{

    int * p = (int *)0x1100;指针变量p指向内存地址编号为0x1100的空间,是特定的物理内存地址

    std::cout << *p << std::endl;访问野指针报错,即访问了非法的内存空间

    system("pause");

    return 0;

}

总结:空指针和野指针都不是我们申请的空间,因此不要访问。

野指针原因

一 指针未初始化

int* p;

p没有初始化,意味着没有明确指向

一个局部变量不初始化的话,放的是随机值:如0xcccccccc

此时p指向的地址是0xcccccccc

*p = 10;

此时,非法访问内存了,这里的p就是野指针

二 指针越界访问

int arr[5] = {0}; 设置一个初始化的数组

int* p =arr; p指向数组第一个元素arr[0]

for(int i = 0; i <= 10; i++)

{

    *p = 1;

    p++;

}

当指针指向的范围超出数组的范围时,p就是野指针

int* test ()

{

    int a = 10;

    return &a;

}

int main()

{

    int* p = test();

返回a的地址,但因为a为局部变量,出函数后其所占内存空间就自动销毁,即不再属于这个程序。

此时通过地址访问不了a的值*a,但如果此空间没有被覆盖使用,则a的值仍然存在

注意区分返回类型是否为引用&,如是函数可返回将亡值

验证:

if (p != NULL) p空出来的空间没有被使用

{

    printf("%d",*p);

}

可打印p的值10

但如果在调用函数后增加一个printf打印,则因销毁的空间可能被占用导致接下来打印别的值

}

三 如何规避野指针

一 指针初始化

二 小心指针越界

三 指针指向空间释放及时 放置NULL

四 避免返回局部变量的地址

五 指针使用之前检查有效性

const修饰指针

const修饰指针有三种情况:

const修饰指针  --- 常量指针

const修饰常量  --- 指针常量

const即可以修饰指针,又可以修饰常量

如下一个程序进一步说明:

int main()

{

    int a = 10;

    int b = 10;

     const int * p1 = &a; 当 const修饰的是常量指针,指针指向可以改,指针指向的值不可以更改

    p1 = &b; p1指向其他内存地址,正确

    *p1 = 100;  p1指向的内存地址上的值进行修改,错误

    int * const p2 = &a;  const修饰的是指针常量,指针指向不可以改,指针指向的值可以更改

    p2 = &b; p2指向其他内存地址,错误

    *p2 = 100; p2指向的内存地址上的值进行修改,正确

    const int * const p3 = &a; const既修饰指针又修饰常量

    p3 = &b; 错误

    *p3 = 100; 错误 此时指针指向和指针指向的值都不可修改

    system("pause");

    return 0;

}

指针运算

一  指针+-整数

 *vp++解析: 

第一步 解引用 *vp

第二步 vp++

 *vp++ = 0 解析:

第一步:先解引用*vp赋值0

第二步: vp++

(*vp)++ 解析:

第一步 找到vp对应地址的值

第二部 该值++

二 指针-指针

int arr[10] = {0};  

printf("%d\n",&arr[9] - &arr[0]);

结果为9

指针-指针得到的是指针和指针之间元素的个数

只有指向同一块空间的指针才能相减 

利用指针实现strlen()函数

方法一

int my_strlen(char* str)

{

    int count = 0;

    while(*str != '\0') \0为内存终止数据

    {

        count++; 计数值加一

        str++; 内存地址加一

    }

    return count;

}

int main()

{

    int len = my_strlen("abcdef")     内存中的实际形式 a b c d e f \ 0

    printf("%d\n",len);

}

方法2 指针-指针

int my_strlen(char* str)

{

   char* start = str; 取start存储数组起始地址

    while (*str != '\0')

    {

        str++ 内存地址加一

    }

    return (str - strart);

}

三 指针关系的运算

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与

指向第一个元素之前的那个内存位置的指针进行比较。

指针和数组

数组:一组相同类型元素的集合

指针变量:是一个变量,存放的是地址

作用:利用指针访问数组中元素

如下一个程序进行详细讲解:

int main()

{

    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

    int * p = arr;   指向数组的指针,也可这样去除取出地址&arr[0]

    std::cout << "第一个元素: " << arr[0] << std::endl;

    std::cout << "指针访问第一个元素: " << *p << std::endl;两者打印结果一致

    for (int i = 0; i < 10; i++)

    {

        std::cout << *p << std::endl; 利用指针遍历数组

        p++; 指针内存后移,指向数组下一位元素

    }

    system("pause");

    return 0;

}

指针和函数

作用:利用指针作函数参数,可以修改实参的值

如下一个程序进行讲解:

//值传递

void swap1(int a ,int b)

{

    int temp = a;

    a = b;

    b = temp;

}

//地址传递

void swap2(int * p1, int *p2)

{

    int temp = *p1;

    *p1 = *p2;

    *p2 = temp;

}

int main()

{

    int a = 10;

    int b = 20;

    swap1(a, b); // 值传递不会改变实参

    std::cout << "a = " << a << std::endl; 打印10

    std::cout << "b = " << b << std::endl;  打印20

    swap2(&a, &b); //地址传递会改变实参

    std::cout << "a = " << a << std::endl; 打印20

    std::cout << "b = " << b << std::endl; 打印10    

    system("pause");

    return 0;

}

总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传。

当数组名传入到函数作为参数时,参数被退化为指向首元素的指针

二级指针

int a = 10;

int* pa = &a; 

pa是一个指针变量,同时是一个一级指针变量

*pa = 20;

*pa指向a的值 

int** ppa = &pa;

ppa是一个二级指针变量  第一个*说明ppa指向的对象pa的类型是int*类型 第二个*说明ppa是指针

**ppa = 20;

二级指针是用来存放一级指针变量的地址

指针数组

存放指针的数组就是指针数组 

int a = 10;

int b = 20;

int c = 30; 

int arr[10]

int* pa = &a;

int* pb = &b;

int* pc = &c;

int* parr[10] = {&a, &b, &c}  数组存储&a,&b,&c

parr就是存放指针的数组

int i = 0;

for(i = 0; i < 3, i++)

{

    printf("%d",*(parr[i]))

}

打印 10 20 30

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值