C语言之初阶指针

简介: /* 本文章 主要记录个人学习,若有误解,还请指出。*/

前言

提示:


/* 本文章 主要记录个人学习,若有误解,还请指出。*/


提示:以下是本篇文章正文内容,下面案例可供参考


一、什么是指针

1.指针

在C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。简单说:指针是用来存放内存地址的指针变量。


简单理解指针的两个要点:

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

指针变量是用来存放地址的指针变量。


2.指针变量概念

定义:可以通过&(取地址操作符)取出某份数据的内存起始地址,这样的一份数据可以是数组、字符串、函数,这个变量就是指针变量。


3.定义指针变量

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

4.指针变量的大小

#include<stdio.h>
int main()
{
  int* pi;
  char* pc;
  float* pf;
  double* pd;
  printf("pi=%d\n",sizeof(pa));
  printf("pc=%d\n",sizeof(pc));
  printf("pf=%d\n",sizeof(pf));
  printf("pd=%d\n",sizeof(pd));
  /*
    pi=4
    pc=4
    pf=4
    pd=4
     不管指针类型如何,大小均为4个字节
   */
}

注:1.一个内存单元的大小为1个字节;

2. 存放在指针中的值都会被当做地址处理;

3. 在32位机器上,地址是由32个0或1组成的二进制bite序列,在64位机器上,地址是由64个0或1组成的二进制bite序列。故指针的大小在32位平台上为4个字节,在64位平台上为8个字节,指针大小与指针类型无关。


5.指针类型

上面我们提到过,p就是一个指针变量,那么指针类型又分为哪些?

#include<stdio.h>
int main()
{
    char  *pc = NULL;
    int   *pi = NULL;
    short *ps = NULL;
    long  *pl = NULL;
    float *pf = NULL;
    double *pd = NULL;
    //这里可以看到指针的变量是data_type + *;
    /*
    char*类型的指针是为了存放char 类型变量的地址;
    int*类型的指针是为了存放int类型变量地址;
    short*类型的指针是为了存放short类型变量地址;
    ……
    */
  return 0;
  }

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

#include<stdio.h>
int main()
{
    /*指针类型决定访问权限*/
    int a = 0x11223344;//0x表示十六进制,值为四个字节
    int* p = &a;
         *p = 0;  //将0赋值给*p(*为解引用符,用来获取指针指向的数据变量a.)
      //按F10调试,查看调试->窗口->内存(输入&a进行调试),内存的值变为 00 00 00 00,我们会值的4个字节全部变成了0
  char* pc = &a;
    *pc = 0;//按F10调试,查看调试->窗口->内存(输入&a进行调试),内存的值变为 00 33 22 11,我们会发现值的第1个字节变成了0
    return 0;
}

再举一个例子

#include<stdio.h>
int main()
{
  int arr[10] = {0};
  int* p = arr; //数组名为数组首元素的地址
  char* pc = arr;
  printf("p = %p",p);
  printf("p+1 = %p",p+1);
  printf("pc = %p",pc);
  printf("pc+1 = %p",pc+1);
  /* 
    p = 00CFFD30
    p+1 = 00CFFD34      //p+1跳过了4个字节,为一个int类型
    pc = 00CFFD30
    pc+1 = 00CFFD31    //pc+1跳过了1个字节,为一个char类型
  */
return 0;
}

指针类型的意义:指针类型决定了指针解引用的访问权限有多大。


int类型的指针访问权限为4个字节

char类型的指针访问权限为1个字节

float类型的指针访问权限为4个字节

double类型的指针访问权限为8个字节


二、什么是野指针

1.野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。


2.野指针的分类

1.指针未初始化

#include<stdio.h>
int main()
{
    int* p;//p是一个局部的指针变量,局部变量不进行初始化的话,默认是随机值,而随机值是不用进行申请的,
          //故*p访问的地址可能不属于自己,指向的内存单元可能就无法访问。
    *p = 20;
  return 0;
}

2. 指针出现越界访问

数组 (指针)越界访问,是指使用了超过有效范围的偏移量。 如只分配了10个元素的空间,但是访问了第11个元素,就属于越界。

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

3. 指针指向的空间被释放

#include<stdio.h>
int* teset()
{
  int i = 10; //i变量在进入test()函数时被创建,地址会被分配,离开test()函数被销毁,内存地址就会被释放。
  return &i;
}
int main()
{
    int* p = test();
    *p = 20; //这里就属于非法访问了
  return 0;
}

4. 避免野指针

但是如何去规避野指针的出现呢?


1.进行指针初始化

2.小心指针越界

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

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

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


三、指针的运算

1.指针的加减运算

1.指针加上一个整数

#include<stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int* ptr1 = arr;//数组首元素的地址
  int* ptr2 = &arr[2];//数组下标为2的元素的地址
  int* ptr3 = ptr2 + 7;//指针加上整数 (10=3+7)
  printf("ptr2 = %d\n", *ptr2); //ptr = 3
  printf("ptr3 = %d\n", *ptr3); //ptr = 10
  return 0;
}

2.指针减去一个整数

#include<stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int* ptr1 = arr;//数组首元素的地址
  int* ptr2 = &arr[7];//数组下标为7的元素的地址
  int* ptr3 = ptr2 - 2;//指针减去整数 (6=8-2)
  printf("ptr2 = %d\n", *ptr2); //ptr = 8
  printf("ptr3 = %d\n", *ptr3); //ptr = 6
  return 0;
}

3.指针减去指针

指针减去指针得到的是两个指针之间的元素个数

#include<stdio.h>
{
  int arr[5] = {1,2,3,4,5};
  int* ptr1 = &arr[4]; //5
  int* ptr2 = &arr[1]; //2
  printf("%d",ptr1 - ptr2); //3
  return 0;
}

两个指针指向不同的空间,再相减会报错

#include<stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  char c[] = {0};
  /*指针和指针相减的前提是两个指针指向同一块空间 */
  printf("%d\n",&arr[9] - &c[0]); //error  运行会报错,因为指向了不同的数组空间
  return 0;
}

2.指针的关系运算

#include<stdio.h>
int main()
{
    int values[5] = {1,2,3,4,5};
    int* ptr = 0;
    for(ptr = &values[5];ptr > &values[0];)
    {
      *--ptr = 0; 
    }
    return 0;
}

按F10在调试->窗口->监视,进行调试。

image.png

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

#include<stdio.h>
int main()
{
    int values[5] = {1,2,3,4,5};
    int* ptr = 0;
    for(ptr = &values[4];ptr >= &values[0];ptr--)
    {
      *ptr = 0; 
    }
    return 0;
}

按F10调试

image.png

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

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


四、指针和数组

#include<stdio.h>
int main()
{ 
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    for(i=0;i<10;i++)
    {
      printf("%p <==> %p\n", &arr[i], p + i);
    }
  return 0;
}

输出结果:

&arr和p+i都表示为数组下标为i的元素的地址

1.1.1.png

利用指针方法打印数组的元素

#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++) {
      printf("%d ", *(p + i) = i); //0,1,2,3,4,5,6,7,8,9
    }
  return 0;
}

几种数组元素的指针表示方法,如同数学中的交换律,等价交换。

#include<stdio.h>
int main()
{ 
  int arr[10] = {1,2,3,4,5,6,7,8,9};
  //数组名为首元素的地址  传递给指针变量p
  int* p = arr;
  printf("%p <==> %p \n", arr, p);
  printf("%d <==> %d \n", arr[1], p[1]);//数组下标为1的元素
  printf("%d <==> %d \n", arr[2], *(p+2));//数组下标为2的元素
  printf("%d <==> %d \n", *(arr+3), *(p + 3));//数组下标为3的元素
  printf("%d <==> %d \n", *(4 + p), *(p + 4));//数组下标为4的元素
  printf("%d <==> %d \n", arr[5], 5[arr]);//数组下标为5的元素
  printf("%d <==> %d \n", p[6], 6[p]);//数组下标为6的元素
  return 0;
}

五、二级指针

指针变量的地址如何存放?就有了二级指针,用来存放指针变量的地址。

#include<stdio.h>
int main()
{
int num = 2;
  int* pn = &num;//num变量的地址存放在指针变量pn中
  int** ppn = &pn; //把pn的地址存放在ppn中
  /* pn是一个普通指针,而ppn是个二级指针 */
  printf("%d\n", *pn);//*pn == num;//解引用,得到num的值即2
  printf("%p\n", *ppn);//*ppn == pn;//解引用,得到pn的值即&num
  printf("%d\n", **ppn);//**ppn == num;//解引用,得到num的值即2
      return 0;
}

六、指针数组

指针数组本质上是一个数组,用来存放指针的数组。

表现形式如下:

#include<stdio.h>
int main()
{
    int a = 10,b = 20,c = 30;
  int* parr[3] = {&a,&b,&c};//数组parr是一个整型指针数组,每个元素是个整型指针,指针存放a、b、c的地址。
  char* pch[3] = {"c语言","c++语言","java语言"};//pch是一个char*类型指针数组,每个元素是一个char*类型的指针,这些指针存放着其对应字符串的首地址。
  return 0;
}


目录
相关文章
|
9月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
168 0
|
11月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
357 7
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
11月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
1278 9
|
11月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
342 7
|
12月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
12月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
12月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
694 3
|
12月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
12月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
12月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
229 1