C语言——初识指针

一、什么是指针?

指针(Pointer)是编程语言中一种重要的数据类型它,它用于存储变量的内存地址。换句话说,指针指向内存中的某个位置,其内容就是地址,这个地址指向存储的数据,程序可以直接访问和操作内存中的数据,这为动态内存分配、函数传参、数组访问等操作提供了便利和灵活性。总的来说,指针是一种允许直接操作内存地址和数据的数据类型,是实现诸多复杂数据结构和高级编程技术的重要基础。

二、指针变量和地址

在C语言中,指针是存储另一个变量内存地址的变量。每个变量在内存中都有一个地址,而指针存储了该地址,因此可以说指针指向该变量所在的内存地址。

当你创建一个指针变量并将其指向另一个变量时,该指针变量将包含另一个变量的内存地址。通过指针,可以访问和操作存储在该内存位置上的数据。

在内存方面,指针本身也需要存储在内存中。指针变量的大小取决于系统架构,在32位系统中为4字节,在64位系统中为8字节。

2.1指针变量

指针变量是一种特殊类型的变量,它存储的数值时内存地址。换句话说,指针变量包含了一个指向内存中特定位置的地址,而不是实际的数据。通过指针变量,程序可以直接地访问和操作内存中地数据。

在C语言中,声明指针变量时需要指定所指向的数据类型,这样编译器就知道在解引用时应该读取多少个字节的数据。指针变量的声明通常使用下面的语法:

int *ptr //声明一个整型指针。*说明ptr是指针变量,int说明ptr指向的是整型类型的对象。

2.2取地址操作符&

在C语言中,取地址操作符用&符号表示。当它与一个变量一起使用时,会返回该变量的所占内存地址较小的字节的地址,只要知道了第一个字节的地址,就可以根据变量的类型决定访问多少个字节的数据。

2.3解引用操作符*

在C语言中,解引用操作符*用于访问指针指向的内存地址上存储的值。换句话说,通过解引用操作符,我们可以从指针所指向的内存位置中读取或写入数据。

以下是一个简单示例,说明了指针和内存地址之间的关系:

#include<stdio.h>

int main()
{
    int num = 10; //定义一个整型变量
    int *ptr; //定义一个指针变量

    ptr=&num; //将指针指向num的地址

    printf("num的值:%d\n", num);
    printf("num的地址:%p\n", &num);
    printf("ptr存储的地址:%p\n", ptr);
    printf("ptr指向的值:%d\n", *ptr);

    return 0;
}

三、指针变量类型的意义 

3.1指针的解引用

指针变量的类型在C语言中具有重要意义。指针变量的类型决定了指针所指向的内存区域应如何解释和操作。

类型的意义在于指定了指针解引用时应该读取多少字节的数据。例如,如果一个指针声明位指向整数类型(int *ptr),则在解引用操作时,它会读取4个字节的数据。这种指定类型的方式允许编译器知道如何解释指针所指向的内存。

举例:

#include<stdio.h>

int main()
{
    int n=0x11223344;
    char *pc=(char *)&n;
    *pc=0;

    return 0;
}

//通过调试我们可以观察到,以上代码只会将n的第一个字节改为0。

3.2指针+-整数

在C语言中,指针与整数相加减是一种常见的操作,它可以用于移动指针指向的内存位置。这种操作的结果取决于指针所指向的数据类型,因为不同类型的数据占用的内存空间大小是不同的。

假设有一个指向某个数据类型的指针ptr,那么ptr+n的结果哦是将指针向后移动n个元素大小的内存空间,即ptr将指向从原始位置开始向后第n个元素的位置。同样的,ptr-n将会将指针向前移动n个元素大小的内存空间。

下面是一个示例,演示了指针与整数相加的操作:

#include<stdio.h>

int main()
{
    int arr[]={10,20,30,40,50};
    int *ptr=&arr[0];// 指针指向数组第一个元素的指针

    printf("初始位置:%p\n", ptr);

    ptr = ptr + 2; // 移动指针到数组的第三个元素
    printf("移动两个位置后:%p\n", ptr);

    return 0;
}

注:需要谨慎使用指针的偏移操作,确保不会越界访问或者指向无效的内存地址。 

3.3void*指针

void*是C语言中一种特殊类型的指针,被称为“无类型指针”。void*可以指向任意类型的数据,因为它的类型被声明为“void”,即没有具体的数据类型。这使得void*具有非常大的灵活性,可以用来实现泛型的数据结构和函数。

需要注意的是,由于编译器无法检查指针指向的数据类型是否匹配,因此,在使用void*指针时,需要确保在解引用之前将其转换为正确的类型。

下面是一个简单的示例,展示了void*指针的基本用法:

#include <stdio.h>

int main() 
{
    int num = 10;
    float f = 3.14;

    void *ptr; // 声明一个 void* 指针

    ptr = &num; // void* 指针指向 int 类型的变量
    printf("指向整数的指针,值为: %d\n", *(int *)ptr); // 解引用时需要将 void* 转换为正                            确的类型

    ptr = &f; // void* 指针指向 float 类型的变量
    printf("指向浮点数的指针,值为: %f\n", *(float *)ptr); // 解引用时需要将 void* 转换为正确的类型

    return 0;
}

总之,void*指针提供了一种非常灵活的机制来处理不同类型的数据,但同时也需要程序员对数据的类型进行严格的管理和转换。

四、const修饰指针

4.1const修饰变量

 在C语言中,可以使用关键字const来修饰变量,从而创建一个常量。被const修饰的变量表示其数值在初始化后不可被修改。这种修饰对于确保数据的不可变性非常有用,并且可以帮助提高代码的可读性和可维护性。

以下是const修饰变量的基本用法:

const int AGE = 30; // 声明一个整型常量
const float PI = 3.14; // 声明一个浮点型常量

int main() 
{
    const int MONTHS_IN_YEAR = 12; // 声明一个局部作用域的整型常量
    // AGE = 40; // 这里会导致编译错误,因为常量不可修改

    return 0;
}

在上面的例子中,AGE、PI和MONTH_IN_YEAR都是被修饰为常量的变量,它们在定义后不可以被修改。任何对这些变量的修改操作都将导致编译错误。

另外,const修饰符也可以用于函数参数,指定参数为只读,并防止在函数内部修改它们的值。这在函数参数传递过程中可以提供额外的安全性。

注:如果我们使用的是变量的地址去修改n,可以操作成功,但这打破了语法规则。

4.2const修饰指针变量

 当const用于修饰指针变量时,它可以有两种不同的用法:一种是将指针本身声明为常量,另一种是将指针指向的数据声明为常量。

将指针本身声明为常量:

int num = 5;
int *const ptr = &num;

在上述代码中,ptr被声明为一个常量指针,它指向num的地址,并在程序的执行过程中不能指向其它位置。这一位置ptr本身的值(即存储的地址)是不可更改的。

将指针指向的数据声明为常量:

int num1 = 10;
const int *ptr1 = &num1;

在上述代码中,ptr1是一个指向整数的指针,但由于被const修饰,它指向的数据是常量,不能通过ptr1来修改num1的值。

将指针本身和指针指向的数据都被声明为常量:

const int *const ptr2 = &num;

在上述代码中,ptr2是一个常量指针,它指向的数据也是常量,因此既不能更改ptr2的指向,也不能通过ptr2来修改它所指向的数据。 

五、野指针

“野指针”是一个常见的术语,用于描述指向无效内存地址的指针。这种指针可能是因为指向的内存已经被释放或未初始化,但指针本身却保留了之前指向内存地址的值。

野指针通常是由编程错误引起的,如果程序尝试访问一个野指针所指向的内存,可能会导致程序崩溃或产生未定义的行为。

在C语言中,避免野指针的最佳方式是在定义指针时立即将其初始化为NULL,并在不再需要指针时将其设置为NULL或重新分配内存,这样可以确保指针不会继续指向一个无效的地址。

以下是一些典型导致野指针问题的情况:

没有正确初始化指针:在定义指针后,未将其初始化为有效地址,导致它成为了野指针。

int *ptr; // 未初始化的指针
*ptr = 10; // 尝试给未初始化的指针赋值

释放内存后未将指针置空:在释放了指针所指向的内存后,未将指针本身置为NULL,导致它成为了野指针。

int *ptr = (int*)malloc(sizeof(int)); // 分配内存
free(ptr); // 释放内存
*ptr = 20; // 尝试访问已经释放的内存

返回局部变量的指针:当函数返回一个指向局部变量的指针时,这个指针在函数返回后将会变成野指针,因为局部变量的内存会被释放。

int *getLocalPointer() 
{
    int a = 5;
    return &a; // 返回了一个指向局部变量的指针
}

int main() 
{
    int *ptr = getLocalPointer(); // 获取一个指向局部变量的指针
    *ptr = 10; // 尝试访问已经释放的内存
    return 0;
}

指针越界访问:指针在访问内存时超出了合法的范围,即指针指向的内存区域之外。

#include <stdio.h>

int main() 
{
    int numbers[] = {1, 2, 3, 4, 5};
    int *ptr = numbers;

    // 指针越界访问
    for (int i = 0; i <= 5; i++) 
    {
        printf("%d\n", *ptr);
        ptr++;
    }

    return 0;
}

避免野指针问题对于编写安全的和健壮的程序至关重要。当出现野指针问题时,调试和排查可能会非常困难,因此尽量避免引入野指针问题是十分重要的。

更多C语言相关练习请访问:test_c: C语言练习,包括常见语法练习以及小项目练习。 (gitee.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值