0


指针小课堂

一.内存和地址

说到内存,内存就像一栋宿舍楼,而每一楼都有这十几二十个房间,每个房间都能住好几个人。那如果我们需要寻找某一个房间的时候,我们需要怎么找呢?答案自然是通过房间号来找了。房间号我们也称之为地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字:指针。

相对的:
内存相当于一栋宿舍楼

内存单元相当于一个房间每个内存单元取1个字节

比特位相当于一个学生(1个字节能放8个比特位)

就像这样:

所以内存单元编号=地址=指针

二.指针变量和地址

1.取地址操作符(&)

在C语⾔中创建变量其实就是向内存申请空间

#include<stdio.h>
int main() {
    int a = 10;
    printf("%p", &a);
    return 0;
}

我们打印出a的地址,如图:

再看一下他的内存所在,可以看到,其一共占用4个字节,因为a是int型的,而且值也恰好是10(0a十六进制转换为十进制为10)。

2.指针变量和解引⽤操作符(*)

2.1指针变量

我们在上面介绍了&操作符,那我们拿到了地址要怎么存放呢?

答案是用指针变量

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

#include<stdio.h>
int main() {
    int a = 10;
    int* pa = &a;//指针变量存放地址
    printf("%p\n", &a);
    printf("%p", pa);
    return 0;
}

可以看到两地址都相等:

2.2如何理解指针类型

    int a = 10;
    int* pa = &a;

‘ *** '代表着pa是一个指针变量**

int则说明pa是整型类型

**(注意:*可以写在左边一点,也可以右边一点,都是正确的),**如:

int *pa=&a;
int* pa=&a;

指针类型不仅仅只有int型的还有:
字符型指针charchar* p1短整型指针shortshort* p1整型指针intint* p1长整型指针longlong* p1单精度浮点型指针floatfloat* p1双精度浮点型指针doubledouble* p1
(注意指针变量的类型要与变量的基本类型相同)

对于这么多的类型,我们来查看一下他们的内存大小:

#include<stdio.h>
int main()
{
    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(long*));
    printf("%d\n", sizeof(float*));
    printf("%d\n",sizeof(double*));
    return 0;
}

不同平台的运行结果:

总结:

** 无论是哪一种平台下计算的结果,每种指针类型的内存大小都一样,都是4或8个字节。**

2.3解引用操作符

解引用操作符用于获取指针所指向的对象或变量的值

解引⽤操作符(*****)。

两个例子:

int x = 10;
int* ptr = &x; // ptr 是指向 x 的指针
int y = *ptr;  // 解引用 ptr,获取 x 的值,y 现在是 10
int a = 100;
int* pa = &a;//pa指向a的地址
*pa = 0;//解引用pa,通过pa中存放的地址,找到指向的空间,
//*pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0

至于为什么弄这么复杂,直接一点定义一个变量直接赋值,或者直接让它等于零就行了,为什么还有多此一举绕来绕去呢?

当然我们可以这么做,但是的话我们多一种方法多一种途径来给他赋值或者干嘛的,何乐而不为呢,学会之后届时我们写代码的时候就可以更加灵活了。

2.4 指针的解引用

对比下面两个代码:
(1)

#include <stdio.h>
int main()
{
    int n = 0x11223344;十六进制转换为十进制结果为287454020
    int* pi = &n;
    printf("%p\n", pi);
    *pi = 0;
    printf("%d", n);//0
    return 0;
}

结果为:

pi在内存中的地址和字节:

(2)

#include <stdio.h>
int main()
{
    int n = 0x11223344;//十六进制转换为十进制结果为287454020
    char* pc = (char*)&n;//强制转换为char*型
    printf("%p\n", pc);
    *pc = 0;
    printf("%d", n);//287453952
    return 0;
}

结果为:

'

pc在内存中的地址和字节:

通过调试我们可以看到,代码(1)会将n的4个字节全部改为0,但是代码(2)只是将n的第⼀个字节改为0。

**总结: 指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。 ⽐如: char 的指针解引⽤就只能访问⼀个字节,⽽ int 的指针的解引⽤就能访问四个字节。

2.5.不同指针类型的运加减性质

在许多编程语言中(例如C和C++),指针与整数相加或相减是一个常见的操作。这个操作可以用来在内存中遍历数组或数据结构。

2.5.1指针与整数相加
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // ptr 指向数组的第一个元素,即 arr[0].注意单个数组名一般指向数组的第一个元素
ptr = ptr + 2; // 现在 ptr 指向 arr[2],即 30

解释:

  • 当你将一个指针与一个整数相加时,结果是一个新的指针,它指向原始指针指向的内存地址之后的某个位置。
  • 如果指针指向的是一个数组的元素,那么指针加上整数 n 将指向数组中从当前元素开始的第 n 个元素。
2.5.2指针与整数相减
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = &arr[3]; // ptr 指向数组的第四个元素,即 arr[3]
ptr = ptr - 2; // 现在 ptr 指向 arr[1],即 20

解释:

  • 当你将一个指针与一个整数相减时,结果是一个新的指针,它指向原始指针指向的内存地址之前的某个位置。
  • 如果指针指向的是一个数组的元素,那么指针减去整数 n 将指向数组中从当前元素开始的第 n 个之前的元素。
2.5.3指针运算的实际地址

解释:

  • 由于指针运算考虑了指针所指向数据类型的大小sizeof(类型),这意味着 ptr + 1 实际上是增加了 sizeof(类型) 个字节,而不是简单的增加 1
  • 例如,如果 ptr 是一个 int* 类型的指针,假设 int 类型占用 4 个字节,那么 ptr + 1 实际上是将 ptr 的地址增加了 4 个字节。

代码示例:

#include <stdio.h>
int main()
{
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return 0;
}

结果如图:

2.5.4指针运算的用法

遍历数组:指针运算可以用于遍历数组中的元素。

#include <stdio.h>
int main()
{
    int arr[5] = { 10, 20, 30, 40, 50 };
    int* ptr = arr;
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(ptr + i)); // 输出数组中的每一个元素
    }

    return 0;

结果如图:

指向结构体成员:指针运算还可以用于遍历结构体数组中的元素。

三.void*指针

void*

指针在C和C++中是一种通用指针类型,表示它可以指向任意类型的数据

void*

指针本身不包含类型信息,只是一个内存地址,因此不能直接解引用进行指针运算

在将

void*

指针传递给其他函数时,通常需要将其转换为具体类型的指针。类型转换使用类型转换运算符

(type*)

void* ptr;
int x = 10;
ptr = &x; // void* 指向 int 类型变量

int* intPtr = (int*)ptr; // 将 void* 转换为 int* 类型
printf("%d\n", *intPtr); // 解引用 int* 类型指针,输出 10
  • void* 指针常用于需要接受不同类型数据的函数参数。
  • 例如,一个通用的比较函数可以使用 void* 指针来比较不同类型的值:
int compare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

qsort(arr, 5, sizeof(int), compare); // 使用 qsort 排序 int 类型数组

注意事项:


** 不能直接解引用**:

  • 由于 void* 不包含类型信息,不能直接对其进行解引用操作。必须先将其转换为具体类型的指针,然后才能解引用。
  • 错误示例:
  • void* ptr;int x = 10;ptr = &x;// printf("%d\n", *ptr); // 错误:void* 不能直接解引用不能进行指针运算
  • 由于 void* 指针没有确定的类型大小,不能进行指针算术运算(如 ptr + 1)。必须将其转换为具体类型指针后再进行运算。
  • 错误示例:
  • void* ptr;int arr[5] = {1, 2, 3, 4, 5};ptr = arr;// ptr++; // 错误:void* 不能进行指针运算

四.const 修饰指针

在C和C++中,

const

修饰符可以用来修饰指针及其指向的对象。这可以用来确保代码中的某些值不会被意外修改。

const

可以以几种不同的方式修饰指针

1.

const

修饰指针所指向的对象

const

修饰指针所指向的对象时(注意const在*的左边),表示通过该指针不能修改所指向的对象。这个声明可以解读为“指向

int

的指针是常量”。它意味着指针本身可以改变指向不同的地址,但不能通过该指针修改所指向的值。

int x = 10;
int y = 20;
const int* ptr = &x; // ptr 指向 x

ptr = &y; // 可以改变 ptr 的指向
// *ptr = 30; // 错误:不能通过 ptr 修改 y 的值

2.

const

修饰指针本身

const

修饰指针本身时(注意const在*的右边),表示指针本身是常量不能指向其他地址。这个声明可以解读为“指针是一个常量,指向

int

”。它意味着指针必须在声明时初始化,之后不能改变其指向,但可以通过指针修改所指向的对象的值。

int x = 10;
int* const ptr = &x; // ptr 必须初始化

*ptr = 20; // 可以通过 ptr 修改 x 的值
// ptr = &y; // 错误:不能改变 ptr 的指向

3.

const

同时修饰指针和指针所指向的对象

const

同时修饰指针和指针所指向的对象时(注意int*两边都有const),表示指针所指向的对象不能被修改。这个声明可以解读为“指向

int

的常量指针是常量”。它意味着指针必须在声明时初始化,之后不能改变其指向,也不能通过该指针修改所指向的对象的值。

int x = 10;
const int* const ptr = &x; // ptr 必须初始化

// *ptr = 20; // 错误:不能通过 ptr 修改 x 的值
// ptr = &y; // 错误:不能改变 ptr 的指向

五.野指针

5.1野指针成应

(1). 指针未初始化

#include <stdio.h>
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
 *p = 20;
 return 0;
}

(2).指针越界访问

#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<=11; i++)
 {
 //当指针指向的范围超出数组arr的范围时,p就是野指针
 *(p++) = i;
 }
 return 0;
}

(3).指针指向的空间释放

#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
printf("%d\n", *p);
 return 0;
}

5.2规避野指针

1.指针初始化

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。

初始化如下:

#include <stdio.h>
int main()
{
 int num = 10;
 int*p1 = &num;
 int*p2 = NULL;
 
 return 0;
}
2**.小心**指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

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

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL

因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL

我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起 来。

不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去 使⽤。

int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<10; i++)
 {
 *(p++) = i;
 }
 //此时p已经越界了,可以把p置为NULL
 p = NULL;
 //下次使⽤的时候,判断p不为NULL的时候再使⽤
 //...
 p = &arr[0];//重新让p获得地址
 if(p != NULL) //判断
 {
 //...
 }
 return 0;
}
4.避免返回局部变量的地址

#include<stdio.h>
int* test()
{
    int a = 0;//局部变量a出了test函数就会被销毁
    return &a;
}
int main()
{
    int* p = test();
    printf("%d\n",*p);
    return 0;
}

因为出了test函数,局部变量a就已经被销毁了,本来属于局部变量a的地址,现在却已经不是他的了。此时这块地址的指向是不确定的

六.assert断言

assert

断言是一种用于在开发和调试阶段检测程序错误的工具。它在C和C++(以及其他编程语言如Python)中被广泛使用,以验证程序运行时的假设是否为真。如果断言失败程序会中止执行,并通常会显示错误信息

使用

assert

的步骤

  1. 包含头文件:- 在使用 assert 之前,需要包含头文件 <assert.h>
  2. 使用 assert:- assert 宏用于检查表达式是否为真。如果表达式为假,程序会终止并显示错误信息,包括表达式、文件名和行号。

基本运用:

#include <stdio.h>
#include <assert.h>

int main() {
    int x = 5;
    assert(x == 5); // 如果 x 不等于 5,程序将终止

    printf("x is 5\n");

    x = 10;
    assert(x == 5); // 这一行将导致程序终止,因为 x 不等于 5

    printf("This line will not be executed\n");

    return 0;
}

检查指针是否为 NULL:

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

int main() {
    int* ptr = (int*)malloc(sizeof(int));
    assert(ptr != NULL); // 检查内存分配是否成功

    *ptr = 42;
    printf("Value: %d\n", *ptr);

    free(ptr);
    ptr = NULL; // 释放内存并将指针置为 NULL

    assert(ptr == NULL); // 检查指针是否为 NULL

    return 0;
}

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

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移 除这条 #define NDEBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语 句。

完!

点个赞吧,感谢阅读!

标签: visualstudio c语言

本文转载自: https://blog.csdn.net/2202_75357702/article/details/140912466
版权归原作者 小容小容 所有, 如有侵权,请联系我们删除。

“指针小课堂”的评论:

还没有评论