天天看点

C/C++指针讲解

指针是一种数据类型

指针变量

指针是一种数据类型,占用内存空间,用来保存内存地址。

void test01()
{

    int* p1 = ;
    int*** p2 = ;

    printf("p1 size:%d\n",sizeof(p1));
    printf("p2 size:%d\n",sizeof(p2));

    //指针是变量,指针本身也占内存空间,指针也可以被赋值
    int a = ;
    p1 = &a;

    printf("p1 address:%p\n", &p1);
    printf("p1 address:%p\n", p1);
    printf("a address:%p\n", &a);
}
           

野指针和空指针

空指针

标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。

对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未执行任何东西,因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NULL指针。

如果对一个NULL指针间接访问会发生什么呢?结果因编译器而异。

不允许向NULL和非法地址拷贝内存:

void test()
{
    char *p = NULL;
    //给p指向的内存区域拷贝内容
    strcpy(p, "1111"); //err

    char *q = ;
    //给q指向的内存区域拷贝内容
    strcpy(q, "2222"); //err        
}
           

野指针

在使用指针时,要避免野指针的出现:

野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

什么情况下回导致野指针?

  • 指针变量未初始化

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

  • 指针释放后未置空

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

  • 指针操作超越变量作用域

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

void test()
{
    int* p = ; //未初始化
    printf("%p\n",p);
    *p = ;
}
           

操作野指针是非常危险的操作,应该规避野指针的出现:

  • 初始化时置 NULL

指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。

  • 释放时置 NULL

当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。

间接访问操作符

通过一个指针访问它所指向的地址的过程叫做间接访问,或者叫解引用指针,这个用于执行间接访问的操作符是*。

注意:对一个int*类型指针解引用会产生一个整型值,类似地,对一个float*指针解引用会产生了一个float类型的值。

int arr[5];
int *p = * (&arr);
int arr1[5][3] arr1 = int(*)[3]
&arr1
           
  • 在指针声明时,* 号表示所声明的变量为指针
  • 在指针使用时,* 号表示操作指针所指向的内存空间
    1. *相当通过地址(指针变量的值)找到指针指向的内存,再操作内存
    2. *放在等号的左边赋值(给内存赋值,写内存)
    3. *放在等号的右边取值(从内存中取值,读内存)
//解引用
void test01()
{
    //定义指针
    int* p = NULL;
    //指针指向谁,就把谁的地址赋给指针
    int a = ;
    p = &a;
    *p = ;//*在左边当左值,必须确保内存可写
    //*号放右面,从内存中读值
    int b = *p;
    //必须确保内存可写
    char* str = "hello world!";
    *str = 'm';

    printf("a:%d\n", a);
    printf("*p:%d\n", *p);
    printf("b:%d\n", b);
}
           

指针的步长

指针是一种数据类型,是指它指向的内存空间的数据类型。指针所指向的内存空间决定了指针的步长。指针的步长指的是,当指针+1时候,移动多少字节单位。

思考如下问题:

int a = ;
unsigned int *p1 = &a;
unsigned char *p2 = &a;

//为什么*p1打印出来正确结果?
printf("%x\n", *p1);
//为什么*p2没有打印出来正确结果?
printf("%x\n", *p2);

//为什么p1指针+加了字节?
printf("p1  =%d\n", p1);
printf("p1+1=%d\n", p1 + );
//为什么p2指针+加了字节?
printf("p2  =%d\n", p2);
printf("p2+1=%d\n", p2 + );
           

指针的意义_间接赋值

间接赋值的三大条件

通过指针间接赋值成立的三大条件:

  1. 2个变量(一个普通变量一个指针变量、或者一个实参一个形参)
  2. 建立关系
  3. 通过 * 操作指针指向的内存
void test()
{
    int a = ;    //两个变量
    int *p = NULL;
    //建立关系
    //指针指向谁,就把谁的地址赋值给指针
    p = &a;
    //通过*操作内存
    *p = ;
}

           

如何定义合适的指针变量

void test()
{
    int b;  
    int *q = &b; //0级指针
    int **t = &q;
    int ***m = &t;
}
           

间接赋值:从0级指针到1级指针

int func1(){ return ; }

void func2(int a)
{
    a = ;
}
//指针的意义_间接赋值
void test02()
{
    int a = ;
    a = func1();
    printf("a = %d\n", a);

    //为什么没有修改?
    func2(a);
    printf("a = %d\n", a);
}

//指针的间接赋值
void func3(int* a)
{
    *a = ;
}

void test03()
{
    int a = ;
    a = func1();
    printf("a = %d\n", a);

    //修改
    func3(&a);
    printf("a = %d\n", a);
}
           

间接赋值:从1级指针到2级指针

void AllocateSpace(char** p)
{
    *p = (char*)malloc();
    strcpy(*p, "hello world!");
}

void FreeSpace(char** p)
{

    if (p == NULL)
    {
        return;
    }
    if (*p != NULL)
    {
        free(*p);
        *p = NULL;
    }

}

void test()
{

    char* p = NULL;

    AllocateSpace(&p);
    printf("%s\n",p);
    FreeSpace(&p);

    if (p == NULL)
    {
        printf("p内存释放!\n");
    }
}
           

间接赋值的推论

  • 用1级指针形参,去间接修改了0级指针(实参)的值。
  • 用2级指针形参,去间接修改了1级指针(实参)的值。
  • 用3级指针形参,去间接修改了2级指针(实参)的值。
  • 用n级指针形参,去间接修改了n-1级指针(实参)的值。

指针做函数参数

指针做函数参数,具备输入和输出特性:

  • 输入:主调函数分配内存
  • 输出:被调用函数分配内存

输入特性

void fun(char *p /* in */)
{
    //给p指向的内存区域拷贝内容
    strcpy(p, "abcddsgsd");
}

void test(void)
{
    //输入,主调函数分配内存
    char buf[] = {  };
    fun(buf);
    printf("buf  = %s\n", buf);
}
           

输出特性

void fun(char **p /* out */, int *len)
{
    char *tmp = (char *)malloc();
    if (tmp == NULL)
    {
        return;
    }
    strcpy(tmp, "adlsgjldsk");

    //间接赋值
    *p = tmp;
    *len = strlen(tmp);
}

void test(void)
{
    //输出,被调用函数分配内存,地址传递
    char *p = NULL;
    int len = ;
    fun(&p, &len);
    if (p != NULL)
    {
        printf("p = %s, len = %d\n", p, len);
    }
}
           

继续阅读