C语言指针详解(上)

简介: C语言指针详解(上)

1.指针是什么?

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

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

我们通过图片来理解这句话:

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

我们再来了解一下指针变量的概念:

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

看代码:

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

总结

指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。

对于 32 位的机器,假设有 32 根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是( 1 或者 0 );

那么 32 根地址线产生的地址就会是:

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

...

11111111 11111111 11111111 11111111

这里就有 2 的 32 次方个地址。

每个地址标识一个字节,那我们就可以给 ( 2^32Byte == 2^32/1024KB ==

2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB ) 4G 的空间进行编址。

同样的方法,那 64 位机器,就是给 64根地址线,就是2^32*4GB的空间

在 32 位的机器上,地址是 32 个 0 或者 1 组成二进制序列,那地址就得用 4 个字节的空间来存储,所以一个指针变量的大小就应该是 4 个字节。

那如果在 64 位机器上,如果有 64 个地址线,那一个指针变量的大小是 8 个字节,才能存放一个地址

888.png

总结:

指针变量是用来存放地址的,地址是唯一标示一个内存单元的。

指针的大小在32位平台是4个字节,在64位平台是8个字节。

2.指针和指针类型

指针的定义方式是:type + *,例如:        

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

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

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

double*类型的指针是为了存放 duoble 类型变量的地址。

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

2.1指针的解引用

下面我们有图有真相:

999.png

通过两图对比我们发现:指针类型的决定了:指针在进行解引用操作时访问几个字节(权限)。

int* 类型的指针在解引用时访问4个字节;

shot* 类型的指针在解引用时访问2个字节;

char* 类型的指针在解引用时访问1个字节;

double* 类型的指针在解引用时访问4个字节;

2.2指针±整数

10.png

这里举了指针+整数的栗子,- 整数也是一样的,只不过是往低地址走。

在这里的指针类型决定了指针的步长(向前/向后走一步有多大距离)。

int*指针 +1,意思是跳过一个整型的大小,也就是向后走4个字节;

short*指针 +1,意思是跳过一个短整型的大小,也就是向后走2个字节;

char*指针 +1,意思是跳过一个字符的大小,也就是向后走1个字节;

double*指针 +1,意思是跳过一个浮点型的大小,也就是向后走8个字节;

总结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

指针的类型决定了指针向前或者向后走一步有多大(距离)。

3.野指针

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

3.1野指针成因

1.指针未初始化

#include <stdio.h>
int main()
{
  int* p;//局部变量指针未初始化,默认为随机值
  *p = 20;//此时p改动的不是想要的目标地址
  return 0;
}

2.指针越界访问

#include<stdio.h>
int main()
{
  int arr[10] = { 0 };
  int* p = arr;
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  for (i = 0; i <= sz; i++)//当i=sz时发生数组越界访问
  {
    *p = i;
    p++;
  }
  return 0;
}

3.指针指向的空间释放

#include<stdio.h>
int* test()
{
  int num = 100;
  return &num;
}
int main()
{
  int* p = test();
  *p = 200;
  return 0;
}
//此时p为野指针,因为函数调用后空间会被系统回收,此时num的地址被回收,p改动的不是想要的目标地址

3.2如何规避野指针

1. 指针初始化

2. 小心指针越界

3. 指针指向空间释放,及时置NULL

4. 避免返回局部变量(栈空间)的地址

5. 指针使用之前检查有效性

指针初始化

#include<stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;//明确初始化
  //NULL--0,就是初始化指针的
  int* p = NULL;
  return 0;
}

检查指针有效性

#include<stdio.h>
int main()
{
  int a = 10;
  int* p = NULL;
  //*p = 20;//无法改变
    //空指针的指针变量是不能改值的
  if (p != NULL)//验证
  {
    printf("%d\n", *p);
  }
  return 0;
}

4.指针运算

4.1指针±整数

#define N_VALUES 5
float values[N_VALUES];
float* vp;
//指针±整数;指针的关系运算
int main()
{
  for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
  {
    *vp = 1;
  }
  return 0;
}

图解:

11.png

4.2指针-指针

我们来举个栗子,用指针-指针的方法模拟实现库函数strlen

//模拟实现strlrn
#include<stdio.h>
int my_strlen(char* arr)
{
  char* start = arr;
  while (*arr)
  {
    arr++;
  }
  return arr - start;
}
int main()
{
  char arr[] = "cjcwqr";
  int len = my_strlen(arr);
  printf("%d\n", len);
  return 0;
}

注意:

1.两个指针相减的前提是:指针指向同一块儿连续的空间

2.指针类型不同不能相减

14.png

4.3指针的关系运算

我们先来看两段代码和他们的逻辑:

#define N_VALUES 5
float values[N_VALUES];
float* vp;
int main()
{
  for (vp = &values[N_VALUES]; vp > &values[0];)
  {
    *--vp = 0;
  }
  return 0;
}

图解:

16.png

#define N_VALUES 5
float values[N_VALUES];
float* vp;
int main()
{
  for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
  {
    *vp = 0;
  }
  return 0;
}

  图解:

18.png

两段代码一个是访问数组前的地址,一个是访问后的地址,实际上都是可以顺利执行功能的,但我们还是应该避免第二种写法,因为标准并不保证它可行。

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,

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

5.指针和数组

我们知道了数组名就是数组首元素的地址(两种特殊情况),那我们就可以使用指针来访问一个数组,比如:

#include <stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int* pa = arr;
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    //方法一
    //printf("%d ", *(pa + i));
    //方法二
    printf("%d ", *pa);
    pa++;
  }
  return 0;
}//输出1 2 3 4 5 6 7 8 9 10

6.二级指针

我们还是通过图解来观察理解二级指针:

19.png

前面我们知道了指针变量是用来存放指针的地址的,那么指针变量的地址应该存到哪里呢?

这就需要用到二级指针了

21.png

通过**pp对pp中的地址解引用,找到的是p,**pp访问的就是p,再通过*p对p中的地址解引用,找到的就是a ,*p访问的就是a。

7.指针数组

首先我来问个问题,指针数组是指针呢还是数组呢?

指针数组是数组,是个存放指针的数组。

我们通过用指针数组来模拟二维数组来对其进行讲解 :

//用一维数组模拟一个二维数组
#include<stdio.h>
int main()
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
  int arr4[] = { 4,5,6,7,8 };
  int* arr[] = { arr1,arr2,arr3,arr4 };
  int i = 0;
    //方法一
  //for (i = 0; i < 4; i++)
  //{
  //  int j = 0;
  //  for (j = 0; j < 5; j++)
  //  {
  //    printf("%d ", *(*(arr + i) + j));
  //  }
  //  printf("\n");
  //}
    //方法二
  for (i = 0; i < 4; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d ",arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

我们知道数组名就是首元素的地址(两个例外) ,这里创建的指针数组int* arr[ ]存放的就是每个数组首元素的地址,我们进行循环首先通过arr[i]找到每个数组,再通过arr[i][j]来访问数组中的每个元素,另一种方法也是同理,先通过*(arr+i)找到每个数组,再通过*(*(arr + i) + j)来访问数组中的每个元素,以达到实现模拟二维数组的效果。

好了以上就是今天的全部内容了,对友友们有帮助的话不妨三连加关注走一波,后期会持续更新C语言干货!

目录
相关文章
|
5天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
8天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
8天前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
13天前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
13天前
|
C语言
c语言中的结构体
本文档详细介绍了C语言中结构体的使用方法,包括结构体的基本定义、变量声明与赋值、数组与指针的应用,以及结构体嵌套、与`typedef`结合使用等内容。通过示例代码展示了如何操作结构体成员,并解释了内存对齐的概念。
|
1月前
|
C语言
C语言------指针
这篇文章是关于C语言中指针的实训,通过示例代码展示了指针的基本概念、定义、赋值、使用和传递,以及指针运算和指针在函数参数中的应用,如交换两个变量的值和找出两个数中的较小值。
C语言------指针
|
19天前
|
C语言
C语言结构体赋值的四种方式
本文总结了C语言结构体的四种赋值方式,并通过示例代码和编译运行结果展示了每种方式的特点和效果。
27 6
|
29天前
|
编译器 程序员 Linux
【C语言篇】结构体和位段详细介绍
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
|
29天前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
29天前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。