指针(1)来啦,通通点进来~

        hello~友友们好,我是林林。最近在学习指针,还真是有些头疼啊。下面是我整理的一些学习笔记,我们一起来学习吧。


目录

一、内存和编址

二、指针变量

三、 解引用

四、void*指针

五、const 修饰指针

(1)const修饰变量后,变量无法再被修改。

(2)const 修饰指针变量

六、指针运算

(1)指针+-整数

(2)指针-指针

(3)指针的关系运算

七、野指针

(1)定义

(2)野指针成因:

(3)如何规避野指针:

八、assert断言

(1)头文件:#include ;

(2)确保程序符合指定条件,如果不符合,就报错终⽌运⾏;

(3)好处

(4)缺点

九、传值调用和传址调用

十、写在最后


一、内存和编址

(1)内存分为一个个的内存单元,每个内存单元的大小取一个字节(也就是能够存放二进制中的一个0/1)。每个内存单元有一个编号,即地址。在C语言中,我们将地址叫做指针.

(2)8 bit = 1 byte  此后的内存单位从小到大依次是:KB MB GB TB PB 相邻两个单位之间的进制为1024(2 ^ 10)。

(3)在CPU和内存之间有一组线,叫做地址总线。对于32(或64)位机器,我们可以理解为一共有32(或64)根地址总线,每一根线有两态(0或1),那么32(或64)根线就有2^ 32(或2 ^ 64)种含义,每一种含义代表一个地址。

(4)地址信息被下达给内存后,在内存上可以找到地址对应的数据,通过数据总线可以将数据传入CPU寄存器。

二、指针变量

(1)C语言中创建变量其实是向内存申请空间。比如我们创建一个整型变量a,内存会申请4个字节存放。而& 取出的是4个字节中较小的字节地址,不是全部的地址。

#include <stdio.h>
int main()
{
    int a = 10;
    //%p是专门用来打印地址的,显示的是16进制
    //%x,%X 也是16进制,只不过会将前面的0省略
    printf("%p\n",&a);
    return 0;
}

 (2)我们取出的地址需要放到指针变量中,接下来我们来创建指针变量。

#include <stdio.h>
int main()
{
    int a = 0;
    int * p = &a;
    //这里的p就是指针变量
    return 0;
}

 我们看到指针的类型为int * ,那么如何理解呢?

* 代表变量p 为指针变量 ; 而int 是意为 指针p 所指向的对象的类型是 int 类型。

POINT : 这里我们要清楚: p 为指针变量,指针==地址 ; *p == a 。

(3)指针变量的大小

在32(或64)位环境下,指针变量的大小为4(或8)个字节;

(因为:1字节=8 比特;32 /8 = 4; 64 / 8 = 8。)

指针变量的大小与其类型无关,在相同的环境下相同。

三、 解引用

(1)* 就是解引用操作符

#include <stdio.h>
int main()
{
    int a = 10;
    int * p = &a;
    //*p 的意思是通过p存放的地址,找到p指向的对象。*p == a
    //此操作为解引用,将a的值更改
    * p = 20;
    return 0;
}

(2)指针的类型决定了其解引用时的权限(一次能操作几个字节)

char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

(3)指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)

char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。

四、void*指针

(1)⽆具体类型的指针(或者叫泛型指 针),这种类型的指针可以⽤来接受任意类型地址。

(2)局限性:不能直接进行指针的+-整数解引⽤的运算。

(3)void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,使得⼀个函数来处理多种类型的数据。

五、const 修饰指针

(1)const修饰变量后,变量无法再被修改。

#include <stdio.h>
int main()
{
    int a = 10;
    a = 20; //可以被修改
    const int b = 10;
    b = 20; //不可以被修改
    return 0;
}

这里的b虽然无法被修改,但b仍然是变量,只是在语法上加了限制。

但如果我们通过修改b的地址,就可以达到目的。

#include <stdio.h>
int main()
{
    const int b = 0;
    int * p = &b;
    *p = 10;    //可以将b修改
    return 0;
}

 如果让p拿到b的地址也不能修改b,怎么做呢?

(2)const 修饰指针变量

(1)int const *p = &b;

const如果放在*的左边,修饰的是指针指向的内容(*p 或 b),保证指针指向的内容(b)不能通过指针来改变;但是指针变量本⾝的内容(p指向谁)可变。

(2)int *const p = &b;

const如果放在*的右边,修饰的是指针变量本⾝(p),保证了指针变量的内容(p指向谁)不能修改,但是指针指向的内容(*p 或 b),可以通过指针改变。

(3)int const * const p = &b;

两者都不能改变。

六、指针运算

(1)指针+-整数

#include <stdio.h>
//指针+- 整数 
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    //p存放数组第一个元素的地址
    int *p = &arr[0];
    int i = 0;
    //计算数组的长度
    int sz = sizeof(arr)/sizeof(arr[0]);
    //用循环打印数组的各个元素
    for(i=0; i<sz; i++)
    {
        printf("%d ",*(p+i));//p+i这⾥就是指针+整数 
    }
    return 0;
}

(2)指针-指针

//指针-指针 
#include <stdio.h>
int my_strlen(char *s)
{
    //将字符串首元素的地址存放于p中
    char *p = s;
    while(*p != '\0' )
    p++;
    //指针-指针:计算字符串的长度
    return p-s;
}
int main()
{
    printf("%d\n", my_strlen("abc"));
    return 0;
}

注意:没有 指针+指针 这一说。

(3)指针的关系运算

//指针的关系运算 
#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    //为什么不是<= ? 因为数组的下标是从0开始的
    while(p<arr+sz) //指针的⼤⼩⽐较 
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}

七、野指针

(1)定义

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

(2)野指针成因:

1.指针未初始化;

2.指针越界访问;

3.指针指向的空间释放:

#include <stdio.h>
int* test()
{
    int n = 100;
    return &n;
}

int main()
{
    int*p = test();
    printf("%d\n", *p);
    return 0;
}

(3)如何规避野指针:

1.指针初始化

#include <stdio.h>
int main()
{
    int a = 0;
    int * p = &a;
    //NULL是指针类型,因此是p而不是*p
    p = NULL;
    int * q = NULL;
    return 0;
}

2. ⼩⼼指针越界

3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

if (p != NULL)
{
...
}

4.避免返回局部变量的地址

对应(2)3.

八、assert断言

(1)头文件:#include <assert.h>;

(2)确保程序符合指定条件,如果不符合,就报错终⽌运⾏;

assert(p != NULL);

(3)好处

1.不仅能⾃动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个NDEBUG 。

#define NDEBUG
#include <assert.h>

 2.在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert。在 Release 版本不影响用户使⽤时程序的效率(优化掉了)。

(4)缺点

增加了程序的运行时间。

九、传值调用和传址调用

(1)实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。。所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。

(2)传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。

十、写在最后

友友们~我们一起努力,拿下指针!!

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值