考试C语言,指针不会?看这一篇博客就够了

简介: 最近在看一本关于【C语言】的书,普通语法没什么,就是指针这个地方确实有点难,现在写一点关于我对指针的理解,当然肯定会有理解不到位的地方,希望多多担待。


文章目录


C语言指针

最近在看一本关于【C语言】的书,普通语法没什么,就是指针这个地方确实有点难,现在写一点关于我对指针的理解,当然肯定会有理解不到位的地方,希望多多担待。

1. 默认你有其他语言基础比如Java,Python,C++等语言基础
2. 默认你对计算机有所了解
3. 默认你想学习,而不是当个热闹看

那么到底什么才是指针呢?

指针就是一串代表内存地址的数字,通常使用十六进制来表示。


正因为我们可以直接对内存地址进行操作,所以我们才说C语言功能强大。

说起指针,以我目前所学,我认为暂时可以分为以下几种类型:

  1. 普通指针——指向一个变量的指针
  2. 数组指针——能对数组进行操作的指针
  3. 函数指针——指向函数的指针

pass:为什么数组指针不说是指向数组的指针。


这个原因会在数组指针的地方,对数组进行分析,让你了解数组的形成。这里就不多做赘述了


1. 普通指针

什么是普通指针,普通指针就是指向基本数据类型的指针,比如int 、float等。


1.1 指针的定义

我认为实战是最好的理解方式,所以会有代码以及注释详细理解,不过在你看代码之前,你应该知道这些东西:

  1. 如何定义一个指针
  2. 如何给指针赋值
  3. 给指针赋值后,怎么使用原变量的值

就和定义一个普通变量一样:类型 *变量名

指针变量接收的是变量的内存地址<br> 在C语言中,通过符号**&**来取出变量的内存地址

赋值也是同样的



1.2 指针实例理解

那么你知道了这些知识后,就看代码:

#include<stdio.h>
int main()
{
    int num = 10;
    // 创建一个int类型的变量,并赋值为10
    int* pnum;
    // 创建一个int类型的指针,你还能这样写 int *pnum
    pnum = &num;
    // &num是num在内存空间的内存地址
    // 这句代码是将num的内存地址赋值给pnum
    printf("num的值为:%d\n&num的值为:%p\n*pnum的值为:%d\npnum的值为:%p",num,&num,*pnum,pnum);
    // 将各个值都打印出来看看效果
    return 0;
}

运行结果是:

num的值为:10
&num的值为:0xff8effe0
*pnum的值为:10
pnum的值为:0xff8effe0


1.3 头脑风暴

看了这段代码,是不是对指针有了更深刻的了解了呢?

如果你想学好,就暂停你的进度,思考一下:

  • pnum是什么,他开辟的内存空间是多大
  • *pnum是什么,有什么用
  • &pnum是什么,他的作用是什么

思考之后来看看吧~~

那么我们看着代码和运行结果可以总结出以下内容:

接下来,你就想想,你的身份证号,你的身份证,国家信息系统

  1. num是一个变量,这个变量可以对10进行操作
  • 将10当成你,num是你的名字
  • 声明一个变量后,内存空间会为变量开辟一个内存空间以及内存地址
  • 而你出生后也会有一个身份证号
  1. &num是变量的内存地址,这里&num虽然是内存地址,但是不说&num是num的指针,因为指针是一个变量,俗称指针变量
  • num在内存的内存地址相当于你的身份证号
  • 你的身份证号只是一串数组,抽象存在
  1. pnun是一个指针变量,他的值是是&num,也就是一个普通变量的内存地址
  • 把它当成你的身份证
  • 你的身份账号在上面,就可以通过身份证号(指针)进行操作(买票,办卡等)
  1. *pnum是通过内存地址获取到该内存地址存储的值
  • 就是通过你的身份证在国家系统找到了你
  • 也可以对你进行操作(比如征信,车票等)


1.4 头脑风暴(二)

试想,你如果声明一个变量,并将该指针变量的内存地址给该指针,也就是让指针变量存储的是指针的内存空间,会有什么事情发生?

思考:我们的指针变量是一个存储内存地址的指针,但他同样也还是一个变量,所以也会在内存中有自己的内存地址,而刚好指针存储的就是变量!!等等等等,一拍即合,我们就把指针的内存赋给指针,看看会发生什么!

上代码:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int * p;
    p = &p;
    printf("p=%p\n&p=%p\n*p=%p",p,&p,*p);
    return 0;
}

编译结果:

p=0xfff75af4
&p=0xfff75afc
*p=0x0

哦豁,结果清晰可见,我们也因此产生了一些疑惑,为什么会出现两个内存地址呢?

找到了原因!

昨天是使用手机敲得C代码,因为那时候还在火车上,没法拿电脑

今天使用了电脑,编译器是gcc,编辑器是vs code

重新编译了一下

运行结果如下:

p=0061FECC
&p=0061FECC
*p=0061FECC

欸,这就很舒服了,内存地址是一样的,所以虽然安卓有C语言的编译器,但还是使用电脑吧。



1.5 二级指针

今天使用了电脑,但是我们的代码却是不妥的,因为这里涉及到了一个二级指针

我们也收到了一条警告

assignment to 'int *' from incompatible pointer type 'int **'
翻译:从不兼容的指针类型'int **'赋值给'int *'  

参考大佬的话,p是一级指针,&p是二级指针,那么问题来了,什么是一级指针,什么是二级指针

一级指针就是普通变量的指针

二级指针就是指针变量的指针,也就是指针的指针

就像这样:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int test = 10;  // 普通变量
    int * p  = &test;   // 指向普通变量的指针,也就是一级指针
    int ** pp = &p;     // 指向指针的指针,也就是二级变量
    printf("test的内存地址是%p\np的内存地址是%p\npp的内存地址是%p", &test, &p, &pp);
    return 0;
}

运行结果:

test的内存地址是0061FECC
p的内存地址是0061FEC8
pp的内存地址是0061FEC4

你就可以这么理解:几级指针,就嵌套了几个地址


1.7 指针常量

他和常量指针长得很像,但是他俩却差了很多,首先,指针常量是指针常量,而常量指针是常量指针

与指针变量相区别,就和常量与变量的区别一样。常量是不可改变的,指针常量也是不可改变的,但是指针常量指向的普通变量却不是不可改变的。

也就是说,指针常量是一个常量,而我们在定义普通常量时通常是使用的const或者#define,而定义指针常量是使用

#include <stdio.h>
int main(int argc, char const *argv[])
{
    /* code */
    int num = 10;   // 普通变量
    int *const p = &num;    // 指针常量
    // 错误使用
    int num2 = 11;
    p = &num2;      // 因为是常量,无法再进行赋值
    return 0;
}

这个指针是常量,无法再进行赋值运算。


1.8 常量指针

这是一个指针,只不过他指向的是一个常量。

我们无法通过指针操作常量,但是可以对指针重新赋值。

#include <stdio.h>
int main(int argc, char const *argv[])
{
    const int num = 10; // 常量
    const int *p = &num;  // 常量指针
    // 错误使用
    *p = 20;
    // 正确使用
    int num2 = 20;
    p = &num2;
    return 0;
}


2. 数组指针/指针数组

数组指针是:

指向数组的指针,它本质上还是一个指针,类比普通指针

指针数组是:

一个存放指针的数组,本质上是数组,就如经常说的字符数组,整型数组一样


2.1 数组的理解

数组本质上只是编译器在内存空间上开辟的一连串的内存

而代表数组的变量其实只是这一连串内存空间的第一个元素的内存地址。

所以当你给编译器看一个数组时,他并不是像人一样能看到这个数组的全貌,他只能看到这个数组的第一个元素,并且知道这个元素的内存地址

看看这串代码:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[] = {1, 2, 3, 4};     // 一个数组
    printf("a的内存地址%p\na[0]的内存地址%p", &a, &a[0]);
    return 0;
}

他的运行结果为:

a的内存地址0061FEC0
a[0]的内存地址0061FEC0

相信你对数组有了更深的了解。


2.2 数组强制类型和下标

那么为什么定义数组需要强制类型呢?

拿int类型来说,int类型占用4个字节

在人们眼中的元素位置的+1

相当于编译器眼里的+4(4是类型占用的字节数)

所以才能精准的拿到某个元素

数组下标是怎么定义的呢?为什么下标从0开始

数组的下标也是这么来的,通过对内存地址的相加减来获取

因为编译器只记得数组第一个元素的内存地址

而下标就是让第一个元素的内存+i(i是下标)

通过下标获取元素的过程可以类比为:

  • arr[1] => *(&arr +1)

先让内存地址加下标,再通过指针获取到元素


2.3 数组指针

数组指针就是指向数组第一个元素的指针,相信认真看了2.1和2.2的你能够很快理解

定义一个数组指针

int a[] = {1, 3, 5, 7};     // 一个数组
int (*p)[4] = &a;   // 定义一个指针,指向数组的头元素

通过指针访问第二个数组元素:

printf("访问数组的第二个元素:%d", *(*p+1));

完整代码:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int a[] = {1, 3, 5, 7};     // 一个数组
    int (*p)[4] = &a;   // 定义一个指针,指向数组的头元素
    printf("a的内存地址%p\na[0]的内存地址%p\n", &a, &a[0]);
    printf("访问数组的第二个元素:%d", *(*p+1));
    return 0;
}

运行结果:

a的内存地址0061FEBC
a[0]的内存地址0061FEBC
访问数组的第二个元素:3


2.4 指针数组

指针数组,顾名思义,他是个数组,就如经常说的字符数组,整型数组一样,只不过指针数组的定义方法和存储对象也有亿点点不一样。

定义一个指针数组(以整型为例)

int *pArr[10];  // 定义一个指针数组

要注意与数组指针的定义区别开

数组指针的定义:

int (*arrP)[10];

一定要注意这个括号,这涉及到了*符号的运算优先级,一但写错,就是不同的两个东西了。

简单使用:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int *arr[10];   // 定义一个指针数组
    int arrSize = 10; // 指针数组的长度
    for (int i = 0; i < arrSize; i++)
    {
        arr[i] = &i;  // 将临时地址放在指针数组里
        printf("数组的元素:%p\n数组元素所指向的元素%d\n", *arr[i]);
        /* code */
    }
    /* code */
    return 0;
}

输出结果:

数组的元素:0061FEA0
数组元素所指向的元素0
数组的元素:0061FEA0
数组元素所指向的元素1
数组的元素:0061FEA0
数组元素所指向的元素2
数组的元素:0061FEA0
数组元素所指向的元素3
数组的元素:0061FEA0
数组元素所指向的元素4
数组的元素:0061FEA0
数组元素所指向的元素5
数组的元素:0061FEA0
数组元素所指向的元素6
数组的元素:0061FEA0
数组元素所指向的元素7
数组的元素:0061FEA0
数组元素所指向的元素8
数组的元素:0061FEA0
数组元素所指向的元素9

因为i是临时变量,所以在每次循环之后都会销毁,下次使用再次开辟,所以内存地址是一样的。


3. 函数指针

在我们定义函数的时候,编译器也会在内存空间给函数开辟一个内存,而该内存的首地址就是函数的内存地址,而函数指针就是指向该内存地址的。


3.1 函数

众所周知,C语言是面向过程的语言,或者称函数式编程。

而在C语言中,函数也确实起了很大的作用,在C语言的学习中,你见过最多的可能就是main函数,同时也是你第一个见得函数。

我们来看看这个main函数

int main(){return 0;}

我们把他浓缩成一行,比较好瞅

  • int是返回类型,每个函数都要有这个,不返回东西的函数的返回值类型为void
  • main是函数名,固定的,无法重载
  • 括号里面是参数列表,一般是默认没有,也可以传递void或者int argc, char const *argv[]
  • {}大括号里面是函数的具体实现代码,比如说printf("Hello World!");
  • return 是函数结束的关键字,返回值为0表示程序正确运行,为其他表示有其他异常

切记main函数不要void main(){},这个真的很重要


3.2 指向函数的指针

见名知意,这个东西也是一个指针,只不过他指向的是一个函数,准确来说是函数在内存空间中开辟空间的头地址。

定义也是有亿点点麻烦,不过却也不是不好理解。

定义:

int (*funP)(int num1, int num2);  // 定义一个函数,有两个整型参数

因为运算符优先级的存在,所以我们需要对变量名与*进行首先运算

使用:

#include <stdio.h>
/*
  定义一个两数求和函数
  返回两个数的和的结果
*/
int sum(int num1, int num2)
{
    int ans = num1 + num2;
    return ans;
}
int main(int argc, char const *argv[])
{
    int (*funP)(int num1, int num2);    // 定义一个函数,有两个整型参数
    funP = sum;         // 将函数sum的地址给funP
    int ans = funP(1, 2);   // 使用指针使用函数
    printf("%d", ans);
    return 0;
}


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