C语言的指针(初阶)

简介: C语言的指针(初阶)

前言

今天有收获,别期待明天,活着本来就挺累挺难的,别想太远了,专注现在吧,谁也不能预料接下来会发生什么!难得来世界走一遭,那就朝着自己觉得有趣的方向前进吧,感谢自己的陪伴了我这一生

一、指针是什么

指针是什么?

指针理解的2个要点:

(1)指针是内存中一个最小单元的编号,也就是地址。

(2)平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结:指针就是地址,口语中说的指针通常指的是指针变量。

内存:

  • 首先在16位操作系统上,指针的大小占2个字节;在32位操作系统上,指针的大小占4个字节;在64位操作系统上,指针的大小占8个字节。
  • 我们知道一个字节是有8个比特位,4个比特位可以表示一个十六进制的一个位置上的数,例如二进制数1111,它对应的十进制数就是1*2^3+1*2^2+1*2^1+1*2^0 == 15。15也是对应了十六进制一个位上的最大表示数了(不管是十六进制数,还是八进制数和二进制数都是通过表示数*对应的权重得到的值——该值也是对应了十进制数的值)
  • 所以二进制的4个位也就是4个比特位就可以表示一位十六进制数。例如在32位操作系统上的指针大小是4个字节(32位),那么也就是可以表示8个十六进制的表示数(32/4 == 8)。如下图:在32位操作系统上,一个字节地址就可以存放到一个以十六进制表示的指针中。(编号->地址->指针)。一个字节的内存地址由一个8位的十六进制(32位的二进制)指针变量中。

指针变量

我们可以通过&(取地址操作符)取出变量的内存的地址,把该地址存放到一个变量中,这个变量就是指针变量。

#include <stdio.h>
int main()
{
     int a = 10;//在内存中开辟一块空间
     int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    /*a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
    中,p就是一个之指针变量。*/
     return 0; 
}

总结:

  • 指针是用来存放地址的,地址是唯一标识一块地址空间的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

我们都知道,变量有不同的类型,那么要对应上这些不同的变量类型,也就衍生出了对应不同的变量类型的指针变量类型。

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

(1)char* 类型的指针是为了存放 char 类型变量的地址。

(2)short* 类型的指针是为了存放 short 类型变量的地址。

(3)int* 类型的指针是为了存放 int 类型变量的地址。

那指针类型的意义是什么呢?

(1)指针+-整数

总结:指针的类型决定了指针向前或者向后走一步有多大(跨过多少字节)

(2)指针的解引用

图1:用int指针变量指向int变量

图2:用char指针变量指向int变量

观察图1和图2,不难发现,char型的指针变量在解引用时,只是解了1个字节的大小;而int型指针变量在解引用时,就全部解了4个字节的大小。

总结:指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。比如:char*的指针解引用就只能访问一个字节,而int*就能访问四个字节。

三、野指针

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

1.野指针的成因

1.1 指针未初始化

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

2.2 指针越界访问

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

3.3 指针指向的空间已经释放了

2.如何规避野指针

(1)指针初始化

(2)小心指针越界

(3)指针指向空间释放及时置NULL

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

(5)指针使用之前检查有效性,如下代码:

#include <stdio.h>
int main()
{
    int *p = NULL;
    int a = 10;
    p = &a;
    if(p != NULL)
    {
        *p = 20;
    }
    return 0; 
}

四、指针运算

  • 指针+-整数
  • 指针-指针
  • 指针的关系运算

1.指针+-整数

例1:

#define N_VALUES 5
void main(){
    float values[N_VALUES] = {1.0,2.0,3.0,4.0,5.0};
    float *vp;
    //指针+-整数;指针的关系运算
    for (vp = &values[0]; vp < &values[N_VALUES];)
    {
         *vp++ = 0; 
    }
}

执行过程

例2:思路如上

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int* p = arr;
  int* pend = arr + 9;
  while (p<=pend)
  {
    printf("%d\n", *p);
    p++;
  }
  return 0;
}

2.指针-指针

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  //指针和指针相减的前提:
  //两个指针指向同一块空间(数组)
  printf("%d\n", &arr[9] - &arr[0]);
  return 0;
}

如果是两个指向不同空间的指针就会出问题,所以指针-指针大多数用在数组!如下代码是错误代码:

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  char c[5];
  //指针和指针相减的前提:
  //两个指针指向同一块空间
  printf("%d\n", &arr[9] - &c[0]);//err
  return 0;
}

为什么没有指针+指针呢?我们可以在这里把指针理解为日期,日期-日期和日期+天数都有意义,但是日期+日期就没啥意义了。


例题:运用指针-指针的知识写出计算字符串长度的功能

int my_strlen(char* str)
{
  char* start = str;
  while (*str != '\0')
  {
    str++;
  }
  return str - start;
}
int main()
{
  //strlen(); - 求字符串长度
  //递归
  int len = my_strlen("abc");
  printf("%d\n", len);
  return 0;
}

3.指针的关系运算

void main(){
    for(vp = &values[5]; vp > &values[0];)
    {
        *--vp = 0; 
    }
}

代码简化, 这将代码修改如下:

void main(){ 
    for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) {
        *vp = 0; 
    }
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。

标准规定:

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

五、指针和数组

我们先看一个例子:

可见数组名和数组的首元素的地址是一样的

结论:数组名表示的是数组首元素的地址(有两种情况除外,请观看我的数组文章,里面有详细介绍)

那么代码写成这样就是可行:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问也是可能的,例如:

所以p+i其实计算的就是数组arr下标为i的地址

那我们就可以直接通过指针来访问数组,例如:

int main()
{
     int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
     int *p = arr; //指针存放数组首元素的地址
     int sz = sizeof(arr) / sizeof(arr[0]);
     int i = 0;
     for (i = 0; i<sz; i++)
     {
     printf("%d ", *(p + i));
     }
     return 0; 
}

这里还有一个很好玩的地方:

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
  int* p = arr;//数组名
  printf("%d\n", arr[2]);
  printf("%d\n", p[2]);//p[2] --> *(p+2)
  //[] 是一个操作符  2和arr是两个操作数
  //a+b
  //b+a
  printf("%d\n", 2[arr]);
  printf("%d\n", arr[2]);
  //arr[2] --> *(arr+2)-->*(2+arr)-->2[arr]
  //arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) == 2[arr]
  //2[arr] <==> *(2+arr)
  return 0;
}

先定义有int arr[10];int* p = arr

(1)因为arr(数组名)其实也是一个一个地址,而且是首元素的地址,p也是指向首元素的地址,那么arr+i和p+i都是相等的。

(2)这里刷新了我对[ ]的认识,不管是arr[2]、2[arr]、p[2]、2[p],在编译时都是转换成了*(arr+2)或者*(p+2)。

六、二级指针

int main()
{
  int a = 10;
  int* pa = &a;//pa是指针变量,一级指针
  //ppa就是一个二级指针变量
  int ** ppa = &pa;//pa也是个变量,&pa取出pa在内存中起始地址
  int*** pppa = &ppa;//pppa是个三级指针
  return 0;
}

找代码其中的int**ppa = &举例,*ppa表示ppa是一个指针变量,而int*表示该指针变量是指向int*类型的。

七、指针数组

我们直接用一段代码来理解就行了

//指针数组 - 数组
// 
//好孩子
int main()
{
  int arr[10];//整形数组 - 存放整形的数组就是整形数组
  char ch[5];//字符数组 - 存放的是字符
  //指针数组 - 存放指针的数组
  int* parr[5];//整形指针的数组
  char* pch[5];
  return 0;
}

有些人问我,为什么要写博客,你难道不怕别人知道这些知识后,让自己的竞争压力变得更大吗?

其实吧,写博客主要是位了巩固自己的知识体现,这是我写的博客,是我的东西,别人不可能比我更熟悉,写博客对于我来说是一件有成就感的事情。别人来看来学,学不学得会是别人的事,我只是想让自己刚到高兴的同时让学习C语言变得更加有趣。

相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
52 0
|
10天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
58 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
10天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
34 9
|
10天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
32 7
|
20天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
81 12
|
13天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
13天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
43 3
|
14天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
20天前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
42 10
|
13天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
29 1