当前位置:网站首页>013 C语言基础:C指针
013 C语言基础:C指针
2022-06-27 04:04:00 【入狱计划进度50%】
文章目录
一:引入
学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。
实例:
#include <stdio.h>
int main(){
int var1;
char var2[10];
printf("var1 address is : %x \n", &var1);
printf("var2 address is : %x \n", &var2);
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim zhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc zhizhen.c -o zhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./zhizhen
var1 address is : 387d966c
var2 address is : 387d9662
通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。
二:什么是指针
指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。
指针变量声明的一般形式为:type *var-name;
在这里type是指针的基本类型,必须是一个有效的C数据类型,var-name是指针变量的名称,星号是用来指定一个变量是指针。例如:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch /* 一个字符型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
三:如何使用指针
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。
实例:
#include <stdio.h>
int main(){
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储var的地址
printf("value of &var: %x \n", &var);
printf("value of ip: %x \n", ip); // 在指针变量中存储的地址
printf("value of *ip: %x \n", *ip); // 使用指针访问值
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim use_zhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc use_zhizhen.c -o use_zhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./use_zhizhen
value of &var: ae0c8004
value of ip: ae0c8004
value of *ip: 14
通过指针变量取得数据
指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:*pointer;
这里的*称为指针运算符,用来取得某个地址上的数据,请看下面的例子:
#include <stdio.h>
int main(){
int a = 15;
int *p = &a;
printf("%d, %d\n", a, *p); //两种方式都可以输出a的值
return 0;
}
运行结果:
15, 15
假设 a 的地址是 0X1000,p 指向 a 后,p 本身的值也会变为 0X1000,*p 表示获取地址 0X1000 上的数据,也即变量 a 的值。从运行结果看,*p 和 a 是等价的。CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过 *p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”。
假设变量 a、p 的地址分别为 0X1000、0XF0A0,它们的指向关系如下图所示:
p a
--------- ---------
| 0X1000 | ----> | 15 |
|--------| |--------|
0XF0A0 0X1000
通过*p获取数据 通过a获取数据
程序被编译和链接后,a、p 被替换成相应的地址。使用 *p 的话,要先通过地址 0XF0A0 取得变量 p 本身的值,这个值是变量 a 的地址,然后再通过这个值取得变量 a 的数据,前后共有两次运算;而使用 a 的话,可以通过地址 0X1000 直接取得它的数据,只需要一步运算。也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。
指针除了可以获取内存上的数据,也可以修改内存上的数据,例如:
#include <stdio.h>
int main(){
int a = 15, b = 99, c = 222;
int *p = &a; //定义指针变量
*p = b; //通过指针变量修改内存上的数据
c = *p; //通过指针变量获取内存上的数据
printf("%d, %d, %d, %d\n", a, b, c, *p);
return 0;
}
四:C中的NULL指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值是一个良好的编程习惯。赋为NULL值的指针称为空指针。NULL指针是一个定义在标准库中的值为零的常量。
实例:
#include <stdio.h>
int main(){
int *ptr = NULL;
printf("ptr的值是 %x \n", ptr);
return 0;
}
结果:
ptr 的值是 0
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
五:C指针描述
| 概念 | 描述 |
|---|---|
| 指针的算术运算 | 可以对指针进行四种算术运算:++、–、+、- |
| 指针数组 | 可以定义用来存储指针的数组。 |
| 指向指针的指针 | C 允许指向指针的指针。 |
| 传递指针给函数 | 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 |
| 从函数返回指针 | C 允许函数返回指针到局部变量、静态变量和动态内存分配。 |
六:指针的算术运算
6.1:概述:
C指针是一个用数值表示的地址,因此,可以对指针执行算术运算,可以对指针进行四种算术运算:++/–/+/-
假设ptr是一个指向地址1000的整型指针,是一个32位的整数,对该指针进行下列的算术运算。ptr++
在执行完上述的运算之后,ptr将指向位置1004,因为ptr每增加一次,它都将指向下一个整数位置,即当前位置往后移4个字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果ptr指向一个地址为1000的字符,上面的运算会导致指针指向位置1001的字符,上面的运算会导致指针指向位置1001,因为下一个字符位置是在1001。
6.1:递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
实例:
#include <stdio.h>
const int MAX = 3;
int main(){
int var[] = {
10, 20, 30};
int i, *ptr;
ptr = var; // 指针中的数组的地址,此处没有用到取址符&
for(i=0; i<MAX; i++){
printf("address of var[%d] = %x \n", i, ptr);
printf("value of var[%d] = %d \n", i, *ptr);
ptr++; // 移动到下一个位置
}
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim dizeng_zhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc dizeng_zhizhen.c -o dizeng_zhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./dizeng_zhizhen
address of var[0] = 32e597f4
value of var[0] = 10
address of var[1] = 32e597f8
value of var[1] = 20
address of var[2] = 32e597fc
value of var[2] = 30
6.2:递减指针
同样地,对指针进行递减运算,即把值减去其数据类型的字节数。
实例:
#include <stdio.h>
const int MAX = 3;
int main(){
int var[] = {
10, 20, 30};
int i, *ptr;
ptr = &var[MAX -1]; // 指针中最后一个元素的地址,此处需要用到&取址符,只有数组的第一个元素时,不要用到&
for(i=MAX; i>0; i--){
printf("address of var[%d] = %x \n", i, ptr);
printf("value of var[%d] = %d \n", i, *ptr);
ptr--; // 移动到下一个位置
}
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim dijian_zhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc dijian_zhizhen.c -o dijian_zhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./dijian_zhizhen
address of var[3] = 856862dc
value of var[3] = 30
address of var[2] = 856862d8
value of var[2] = 20
address of var[1] = 856862d4
value of var[1] = 10
6.3:指针的比较
指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
下面的程序修改了上面的实例,只要变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:
实例:
#include <stdio.h>
const int MAX = 3;
int main(){
int var[] = {
10, 20, 30};
int i, *ptr;
ptr = var; // 指针中第一个元素的地址
i = 0;
while(ptr <= &var[MAX - 1]){
printf("address of var[%d] = %x \n", i, ptr);
printf("value of var[%d] = %d \n", i, *ptr);
ptr++; // 指向上一个位置
i++;
}
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim bijiao_zhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc bijiao_zhizhen.c -o bijiao_zhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./bijiao_zhizhen
address of var[0] = 79f125d4
value of var[0] = 10
address of var[1] = 79f125d8
value of var[1] = 20
address of var[2] = 79f125dc
value of var[2] = 30
七:指针数组
在指针数组的概念之前,先让我们来看一个实例,它用到了一个由 3 个整数组成的数组:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {
10, 100, 200};
int i;
for (i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, var[i] );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
可能有一种情况,我们想要让数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:
int *ptr[MAX];
在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,
如下所示:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {
10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; /* 赋值为整数的地址 */
}
for ( i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, *ptr[i] );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
也可以用一个指向字符的指针数组来存储一个字符串列表,如下:
#include <stdio.h>
const int MAX = 4;
int main ()
{
char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
int i = 0;
for ( i = 0; i < MAX; i++)
{
printf("Value of names[%d] = %s\n", i, names[i] );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
八:指向指针的指针
是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
声明,即在变量名前放置两个星号:
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,
实例:
#include <stdio.h>
int main(){
int var;
int *ptr;
int **pptr;
var = 3000;
ptr = &var; // 获取var的地址
pptr = &ptr; // 使用运算符&获取ptr的地址
printf("var : %d \n", var);
printf("*ptr : %d \n", *ptr);
printf("**pptr : %d \n", **pptr);
printf("ptr: %x \n", ptr);
printf("pptr: %x \n", pptr);
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim zhizhenzhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc zhizhenzhizhen.c -o zhizhenzhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./zhizhenzhizhen
var : 3000
*ptr : 3000
**pptr : 3000
ptr: cc594804
pptr: cc5947f8
九:传递指针给函数
C语言允许传递指针给函数,只需要简单的声明函数参数为指针类型即可。
下面实例中,我们传递一个无符号的long型指针给函数,并在函数内改变这个值:
#include <stdio.h>
void getSeconds(unsigned long *par);
int main(){
unsigned long sec;
getSeconds(&sec);
printf("Number of seconds: %ld \n", sec); // 输出实际值
return 0;
}
void getSeconds(unsigned long *par){
*par = time(NULL); // 获取当前的秒数
return;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim zhizhenhanshu.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc zhizhenhanshu.c -o zhizhenhanshu
zhizhenhanshu.c: In function ‘getSeconds’:
zhizhenhanshu.c:13:12: warning: implicit declaration of function ‘time’ [-Wimplicit-function-declaration]
13 | *par = time(NULL); // 获取当前的秒数
| ^~~~
┌──(rootkali)-[~/Desktop/c_test]
└─# ./zhizhenhanshu
Number of seconds: 1655260682
十:从函数返回指针
在上一章中,我们已经了解了 C 语言中如何从函数返回数组,类似地,C 允许您从函数返回指针。为了做到这点,您必须声明一个返回指针的函数,如下所示:
int * myFunction(){
}
另外,C不支持在函数外返回局部变量的地址,除非定义局部变量为static变量。
现在,让我们来看下面的函数,会生成10个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们,具体如下:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
// 要生成和返回随机数的函数
int * getRandom(){
static int r[10];
int i;
srand((unsigned)time(NULL)); // 设置种子
for(i=0; i<10; ++i){
r[i] = rand();
printf("%d \n", r[i]);
}
return r;
}
// 要调用上面定义函数的主函数
int main(){
// 一个指向整数的指针
int *p;
int i;
p = getRandom();
for(i=0; i<10; i++){
printf("*(p+[%d]) : %d\n", i, *(p+i));
}
return 0;
}
结果:
┌──(rootkali)-[~/Desktop/c_test]
└─# vim hanshuzhizhen.c
┌──(rootkali)-[~/Desktop/c_test]
└─# gcc hanshuzhizhen.c -o hanshuzhizhen
┌──(rootkali)-[~/Desktop/c_test]
└─# ./hanshuzhizhen
1181024331
473861915
133589365
1181239576
862847673
808881362
1959669527
1283500003
1282139234
1969592223
*(p+[0]) : 1181024331
*(p+[1]) : 473861915
*(p+[2]) : 133589365
*(p+[3]) : 1181239576
*(p+[4]) : 862847673
*(p+[5]) : 808881362
*(p+[6]) : 1959669527
*(p+[7]) : 1283500003
*(p+[8]) : 1282139234
*(p+[9]) : 1969592223
边栏推荐
- ERP demand and sales management Kingdee
- fplan-Powerplan实例
- Penetration test - directory traversal vulnerability
- FastDDS的服务器记录-译-
- 使用promise的基本功能【四、Promise源码】
- JMeter takes the result of the previous request as the parameter of the next request
- ERP需求和销售管理 金蝶
- mysql数据库基础:DQL数据查询语言
- 静态时序分析-OCV和time derate
- WPF 开源控件库Extended WPF Toolkit介绍(经典)
猜你喜欢

基于MobileNet-Yolov4搭建轻量化目标检测

2021:Passage Retrieval for Outside-KnowledgeVisual Question Answering通道检索的外部知识视觉问答

Learn crypto from Buu (Zhou Geng)

CVPR2021:Separating Skills and Concepts for Novel Visual Question Answering将技巧与概念分开的新视觉问答

I found a JSON visualization tool artifact. I love it!

2021:Zero-shot Visual Question Answering using Knowledge Graphs使用知识图的零次视觉问答

Kotlin compose custom compositionlocalprovider compositionlocal

Kotlin compose implicitly passes the parameter compositionlocalprovider

fplan-电源规划

Implementation of window encryption shell
随机推荐
[BJDCTF2020]The mystery of ip
Kotlin compose compositionlocalof and staticcompositionlocalof
[array]bm94 rainwater connection problem - difficult
Cultural tourism light show breaks the time and space constraints and shows the charm of night tour in the scenic spot
Resnet152 pepper pest image recognition 1.0
从某种意义来讲,互联网业已成为了一个孵化器,一个母体
Building lightweight target detection based on mobilenet-yolov4
jmeter将上一个请求的结果作为下一个请求的参数
jmeter分布式压测
Why does C throw exceptions when accessing null fields?
MySql最详细的下载教程
IOS development: understanding of dynamic library shared cache (dyld)
Matlab | drawing of three ordinate diagram based on block diagram layout
Système de collecte des journaux
Matlab | visualization of mathematical properties related to three interesting circles
resnet152 辣椒病虫害图像识别1.0
2020:MUTANT: A Training Paradigm for Out-of-Distribution Generalizationin Visual Question Answering
MobileNet系列(4):MobileNetv3网络详解
Mysql database foundation: DQL data query language
List of best reading materials for machine learning in communication