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;
}


目录
相关文章
|
8天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
11天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
17天前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
1月前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
1月前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
1月前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
1月前
|
C语言
【C语言】指针速览
【C语言】指针速览
16 0
|
1月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
1月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
|
3月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)