C语言-指针了解

2023-05-16

文章目录

    • 一.什么是指针?
    • 二.为什么要使用指针?
    • 二.指针的声明与应用
      • 1.字符指针
      • 2.指针与数组
        • 2.1指针与数组的关系
        • 2.2指针数组
        • 2.3指针数组
        • 2.4数组名 VS 数组名
        • 2.5.数组传参、指针传参
          • 2.5.1 一维数组传参
          • 2.5.2 二维数组传参
          • 2.5.3 一级指针传参
          • 2.5.4 二级指针传参
      • 3.指针与函数
        • 3.1 指针作为函数的参数
        • 3.2 函数指针
          • 3.2.1函数指针
          • 3.2.2 函数指针数组
          • 3.2.2 指向函数指针数组的指针
          • 3.2.2 回调函数
      • 4.指针与结构体
        • 4.1 结构体
        • 4.2 结构指针
      • 5.指针的运算
        • 5.1指针加减整数 :
        • 5.2指针 - 指针

一.什么是指针?

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
 C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量
 
 内存其实就是一组有序字节组成的数组,数组中,每个字节大大小固定,都是 8bit。对这些连续的字节从 0 开始进行编号,每个字节都有唯一的一个编号,这个编号就是内存地址。如下图:
在这里插入图片描述
  这是一个 4GB 的内存,可以存放 2^32 个字节的数据。左侧的连续的十六进制编号就是内存地址,每个内存地址对应一个字节的内存空间。而指针变量保存的就是这个编号,也即内存地址。

总结:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。

二.为什么要使用指针?

在C语言中,指针的使用非常广泛,因为使用指针往往可以生成更高效、更紧凑的代码。

好处:
1)指针的使用使得不同区域的代码可以轻易的共享内存数据,这样可以使程序更为快速高效;
2)C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等;
3)C语言是传值调用,而有些操作传值调用是无法完成的,如通过被调函数修改调用函数的对象,但是这种操作可以由指针来完成,而且并不违背传值调用。

二.指针的声明与应用

指针的声明比普通变量的声明多了一个一元运算符 “”。运算符 “” 是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象。
 声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前,指针必须进行初始化:或是使他指向现有的内存,或者给他动态分配内存,否则我们并不知道指针指向哪儿。

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:


int main()
{
	char ch = 'w';
	char *pc = &ch;
	*pc = 'w'; 
	return 0;
}

还有一种使用方式如下:


int main()
{
	char* pstr = "hello bit.";//将字符串首元素的地址放入pstr中
	printf("%s\n", pstr);
	return 0;
}

代码 char* pstr = “hello bit.”; 特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是其本质是把字符串 hello bit. 首字符的地址放到了pstr中。

在这里插入图片描述
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

例如:

#include <stdio.h>

int main()
{
	char str1[] = "hello bit."; 
	char str2[] = "hello bit."; 
	char *str3 = "hello bit."; 
	char *str4 = "hello bit.";
	if(str1 ==str2)
	printf("str1 and str2 are same\n"); 
	else
	printf("str1 and str2 are not same\n");
	if(str3 ==str4)
	printf("str3 and str4 are same\n"); 
	else
	printf("str3 and str4 are not same\n");
return 0;
}

这里最终输出的是:
在这里插入图片描述
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。 指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开 辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针与数组

2.1指针与数组的关系

在C语言中,指针与数组之间的关系十分密切。实际上,许多可以用数组完成的工作都可以使用指针来完成。一般来说,用指针编写的程序比用数组编写的程序执行速度快,但另一方面,用指针实现的程序理解起来稍微困难一些。

我们先声明一个数组:

int a[10];        // 声明一个int类型的数组,这个数组有10个元素

我们可以用 a[0]、a[1]、…、a[9] 来表示这个数组中的10个元素,这10个元素是存储在一段连续相邻的内存区域中的。

接下来,我们再声明一个指针:

int *p;           // 声明一个int类型的指针变量

p 是一个指针变量,指向内存中的一个区域。如果我们对指针 p 做如下的初始化:

p = &a[0];        // 对指针进行初始化,p将指向数组 a 的第 1 个元素 a[0]

我们知道,对指针进行自增操作会让指针指向与当前元素相邻的下一个元素,即 *(p + 1) 将指向 a[1] ;同样的, *(p + i) 将指向 a[i] 。因此,我们可以使用该指针来遍历数组 a[10] 的所有元素。可以看到,数组下标与指针运算之间的关系是一一对应的。而根据定义,数组类型的变量或表达式的值是该数组第 1 个元素的地址,且数组名所代表的的就是该数组第 1 个元素的地址,故,上述赋值语句可以直接写成:

p = a;        // a 为数组名,代表该数组最开始的一个元素的地址 

很显然,一个通过数组和下标实现的表达式可以等价地通过指针及其偏移量来实现,这就是数组和指针的互通之处。但有一点要明确的是,数组和指针并不是完全等价,指针是一个变量,而数组名不是变量,它数组中第 1 个元素的地址,数组可以看做是一个用于保存变量的容器。更直接的方法,我们可以直接看二者的地址,并不一样:

#include "stdio.h"                                                                          
int main()
{
    int x[10] = {1,2,3,4,5,6,7,8,9,0};
    int *p = x;
    printf("x的地址为:%p\n",x);
    printf("x[0]的地址为:%p\n",&x[0]);
    printf("p的地址为:%p\n",&p);      // 打印指针 p 的地址,并不是指针所指向的地方的地址

    p += 2;
    printf("*(p+2)的值为:%d\n",*p);    // 输出结果为 3,*(p+2)指向了 x[2]
    return 0;
}

结果如下:

在这里插入图片描述

可以看到, x 的值与 x[0] 的地址是一样的,也就是说数组名即为数组中第 1 个元素的地址。实际上,打印 &x 后发现,x 的地址也是这个值。而 x 的地址与指针变量 p 的地址是不一样的。故而数组和指针并不能完全等价。

2.2指针数组

指针是一个变量,而数组是用于存储变量的容器,因此,指针也可以像其他变量一样存储在数组中,也就是指针数组。 指针数组是一个数组,数组中的每一个元素都是指针。声明一个指针数组的方法如下:

int *p[10];    // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向int类型的指针

在上述声明中,由于 [] 的优先级比 * 高,故 p 先与 [] 结合,成为一个数组 p[];再由 int * 指明这是一个 int 类型的指针数组,数组中的元素都是 int 类型的指针。数组的第 i 个元素是 *p[i],而 p[i] 是一个指针。由于指针数组中存放着多个指针,操作灵活,在一些需要操作大量数据的程序中使用,可以使程序更灵活快速。
  
 
其它:

int* arr1[10]; //整形指针的数组 
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

2.3指针数组

我们已经熟悉: 整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够指向浮点型数 据的指针。
那数组指针应该是:能够指向数组的指针

下面代码哪个是数组指针?


int *p1[10]; 
int (*p2)[10];
//p1, p2分别是什么?

解释:

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。

//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

数组指针是一个指针,它指向一个数组。声明一个数组指针的方法如下:

int (*p)[10];        // 声明一个数组指针 p ,该指针指向一个数组

由于 () 的优先级最高,所以 p 是一个指针,指向一个 int 类型的一维数组,这个一维数组的长度是 10,这也是指针 p 的步长。也就是说,执行 p+1 时,p 要跨过 n 个 int 型数据的长度。数组指针与二维数组联系密切,可以用数组指针来指向一个二维数组,如下:

#include "stdio.h"                                                                          
int main()
{
	int arr[2][3] = { 1,2,3,4,5,6 };// 定义一个二维数组并初始化
	int(*p)[3];              // 定义一个数组指针,指针指向一个含有3个元素的一维数组

	p = arr; // 将二维数组的首地址赋给 p,此时 p 指向 arr[0] 或&arr[0][0]
	printf("%d\n", (*p)[0]);// 输出结果为 1
	p++;// 对 p 进行算术运算,此时 p 将指向二维数组的下一行的首地址,即 &arr[1][0]
	printf("%d\n", (*p)[1]);// 输出结果为5
	return 0;
}

2.4数组名 VS 数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。 那&arr数组名到底是啥?
我们看一段代码:


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

运行结果如下:
在这里插入图片描述
可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗?
我们再看一段代码:

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

运行结果如下:
在这里插入图片描述
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下) 数组的地址+1,跳过整个数组的大小,所以 &(arr+1 )相对于 &arr 的差值是40.

数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。 看代码:

#include <stdio.h> int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
	//但是我们一般很少这样写代码
	 return 0;
}

一个数组指针的使用:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收 print_arr2(arr, 3, 5);
	return 0;
}

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10]; 
int (*parr2)[10];
int (*parr3[10])[5];

2.5.数组传参、指针传参

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

2.5.1 一维数组传参

示例:

void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
	int arr[10] = { 0 }; 
	int *arr2[20] = { 0 }; 
	test(arr); 
	test2(arr2);
}

上述传参方式都可以。

2.5.2 二维数组传参

示例:

#include<stdio.h>
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok? 错误
{}
void test(int arr[][5])//ok?
{}
void test(int *arr)//ok? 
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 }; 
	test(arr);
}

总结:
二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。

2.5.3 一级指针传参

示例:

#include<stdio.h>
void print(int *p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int *p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz); 
	return 0;
}
2.5.4 二级指针传参

示例:


#include <stdio.h> void test(int** ptr)
{
	 printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10; int*p = &n;
	int **pp = &p; test(pp);
	test(&p); return 0;
}

3.指针与函数

C语言的所有参数均是以“传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。

3.1 指针作为函数的参数

传值调用的好处是是被调函数不会改变调用函数传过来的值,可以放心修改。但是有时候需要被调函数回传一个值给调用函数,这样的话,传值调用就无法做到。为了解决这个问题,可以使用传指针调用。指针参数使得被调函数能够访问和修改主调函数中对象的值。
例如:

#include "stdio.h"

void swap1(int a, int b)// 参数为普通的 int 变量
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

void swap2(int *a, int *b)// 参数为指针,接受调用函数传递过来的变量地址作为参数,对所指地址处的内容进行操作
{
	int temp;// 最终结果是,地址本身并没有改变,但是这一地址所对应的内存段中的内容发生了变化,即x,y的值发生了变化
	temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int x = 1, y = 2;
	swap1(x, y);              // 将 x,y 的值本身作为参数传递给了被调函数
	printf("%d %5d\n", x, y); // 输出结果为:1     2

	swap(&x, &y);             // 将 x,y 的地址作为参数传递给了被调函数,传递过去的也是一个值,与传值调用不冲突
	printf("%d %5d\n", x, y); // 输出结果为:2     1
	return 0;
}

3.2 函数指针

3.2.1函数指针

在C语言中,函数本身不是变量,但是可以定义指向函数的指针,也称作函数指针,函数指针指向函数的入口地址。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。
声明一个函数指针的方法如下:

返回值类型 (* 指针变量名)([形参列表];

int (*pointer)(int *,int *);        // 声明一个函数指针

上述代码声明了一个函数指针 pointer ,该指针指向一个函数,函数具有两个 int * 类型的参数,且返回值类型为 int。

应用:

#include "stdio.h"
#include "string.h"

int str_comp(const char *m,const char *n);                             // 声明一个函数 str_comp,该函数有两个 const char 类型的指针,函数的返回值为 int 类型
void comp(char *a,char *b,int (*prr)(const char *,const char*));       // 声明一个函数 comp ,注意该函数的第三个参数,是一个函数指针

int main()
{
    char str1[20];      // 声明一个字符数组
    char str2[20];
    int (*p)(const char *,const char *) = str_comp;            // 声明并初始化一个函数指针,该指针所指向的函数有两个 const char 类型的指针,且返回值为 int 类型
    gets(str1);         // 使用 gets() 函数从 I/O 读取一行字符串
    gets(str2);
    comp(str1,str2,p);  // 函数指针 p 作为参数传给 comp 函数

    return 0;
}

int str_comp(const char *m,const char *n)
{
   // 库函数 strcmp 用于比较两个字符串,其原型是: int strcmp(const char *s1,const char *s2);
    if(strcmp(m,n) == 0) 
        return 0;
    else
        return 1;
}

/* 函数 comp 接受一个函数指针作为它的第三个参数 */
void comp(char *a,char *b,int (*prr)(const char *,const char*))
{
    if((*prr)(a,b) == 0)
        printf("str1 = str2\n");
    else
        printf("str1 != str2\n");
} 

这段代码的功能是从键盘读取两行字符串(长度不超过20),判断二者是否相等。

注意,声明一个函数指针时,() 不能漏掉,否则:

 int *p(void *,void*);

这表明 p 是一个函数,该函数返回一个指向 int 类型的指针。

3.2.2 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
下面哪个是呢?

int (*parr1[10]])(); 
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1 。parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的用途:转移表(计算器)

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1; int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf("	1:add	2:sub	\n");
		printf("	3:mul	4:div	\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:"); 
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else
			printf("输入有误\n"); 
	}
	return 0;
}

3.2.2 指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str); 
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[10])(const char*) = &pfunArr; 
	return 0;
}
3.2.2 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这 个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而 是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

首先演示一下qsort函数的使用:

//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
	return (*( int *)p1 > *(int *) p2);
}

int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;

	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i< 		sizeof(arr) / sizeof(arr[0]); i++)
	{
	printf( "%d ", arr[i]);
	}
	printf("\n"); return 0;
}

使用回调函数,模拟实现qsort(采用冒泡的方式)。

#include <stdio.h>

int int_cmp(const void * p1, const void * p2)
{
	return (*(int *)p1 > *(int *)p2);
}

void _swap(void *p1, void * p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char *)p1 + i);
		*((char *)p1 + i) = *((char *)p2 + i);
		*((char *)p2 + i) = tmp;
	}
}

void bubble(void *base, int count, int size, int(*cmp)(void *, void *))
{
	int i = 0; int j = 0;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 0; j < count - i - 1; j++)
		{
			if (cmp((char *)base + j * size, (char *)base + (j + 1)*size) > 0)
			{
				_swap((char *)base + j * size, (char *)base + (j + 1)*size, size);
			}
		}
	}
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	//char *arr[] = {"aaaa","dddd","cccc","bbbb"}; int i = 0;
	bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp); for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n"); return 0;
}

4.指针与结构体

4.1 结构体

结构是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。由于结构将一组相关的变量看做一个单元而不是各自独立的实体,因此结构有助于组织复杂的数据,特别是在大型的程序中。声明一个结构的方式如下:

struct message{            // 声明一个结构 message
    char name[10];             // 成员
    int age;
    int score;  
};

typedef struct message s_message;     // 类型定义符 typedef

s_message mess = {"tongye",23,83};    // 声明一个 struct message 类型的变量 mess,并对其进行初始化 

--------------------------------------------------------------------------------------------------------------
/* 另一种更简便的声明方法 */
typedef struct message{
  char name[10];
  int age;
  int score;
}message;

可以使用 结构名.成员 的方式来访问结构中的成员,如下:

#include "stdio.h"

int main(){
  printf("%s\n",mess.name);    // 输出结果:tongye
  printf("%d\n",mess.age);     // 输出结果:23

  return 0;
}

4.2 结构指针

结构指针是指向结构的指针,以上面的结构为例,可以这样定义一个结构指针:

message *p;        // 声明一个结构指针 p ,该指针指向一个 message 类型的结构
*p = &mess;      // 对结构指针的初始化与普通指针一样,也是使用取地址符 &

C语言中使用 -> 操作符来访问结构指针的成员,举个例子:

#include "stdio.h"

typedef struct{
    char name[10];
    int age;
    int score;  
}message;

int main(){
    message mess = {"tongye",23,83};
    message *p = &mess;

    printf("%s\n",p->mess);      // 输出结果为:tongye
    printf("%d\n",p->score);         // 输出结果为:83

    return 0;
}

5.指针的运算

C 指针的算术运算只限于两种形式:

5.1指针加减整数 :

可以对指针变量 p 进行 p++、p–、p + i 等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i 个操作数。用一张图来说明一下:
在这里插入图片描述
 在上图中,10000000等是内存地址的十六进制表示(数值是假定的),p 是一个 int 类型的指针,指向内存地址 0x10000008 处。则 p++ 将指向与 p 相邻的下一个内存地址,由于 int 型数据占 4 个字节,因此 p++ 所指的内存地址为 1000000b。其余类推。不过要注意的是,这种运算并不会改变指针变量 p 自身的地址,只是改变了它所指向的地址。举个例子:

5.2指针 - 指针

只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。举个例子:

#include "stdio.h"

int main(){
    int a[10] = {1,2,3,4,5,6,7,8,9,0};
    int sub;
    int *p1 = &a[2];
    int *p2 = &a[8];

    sub = p2-p1;                                                                            
    printf("%d\n",sub);    // 输出结果为 6

    return 0;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言-指针了解 的相关文章

  • 到底是选择嵌入式行业还是互联网行业

    不管是在狄泰圈子还是知乎上面 xff0c 关于到底选择嵌入式行业还是互联网行业这类问题 xff0c 是经常拿来讨论的 差不多像女孩子的月假一样 xff0c 每月必有 今天我就发表一下自己的看法 xff0c 希望对有同样问题的小伙伴有所帮助
  • 用CSS怎样实现鼠标移上去背景颜色变色 onmouseover、onmouseout

    lt DOCTYPE html gt lt html lang 61 34 en 34 gt lt head gt lt meta http equiv 61 34 Content Type 34 content 61 34 text ht
  • 软件质量模型--8大特性(学习笔记)

    目录 一 软件质量模型 二 8个特性及其子特性 一 软件质量模型 软件质量模型将系统 软件产品属性划分为8个特性 xff1a 功能性 性能效率 兼容性 易用性 可靠性 信息安全性 维护性和可移植性 与以往的软件质量六大属性 xff0c 区分
  • 【 Caffe C++】ubuntu下创建hdf5数据源解决多标签问题

    如何在ubuntu下为caffe创建HDF5数据源 xff1f hdf5数据源示例 layer span class token punctuation span name span class token operator span sp
  • MarkDownPad2 正版破解与汉化

    用MarkdownPad2写东西看起来比较高大上 就是专业版的竟然要15美刀 所以今天分享个能用的注册码 让大家都能用免费的专业版 首先去官网下载官方版本的Markdown 因为是外国网站所以可能有点慢 我下载时就是这样 所以我传了个官方的
  • VNC连接不上?too many authentication failures

    VNC连接不上 xff1f too many authentication failures 文章目录 VNC连接不上 xff1f too many authentication failures一 VNC连接不上阿里云服务器 一 VNC连
  • 临时抱佛脚真的有用吗?

    对于面试 应该有相应的准备 xff0c 对面试公司的背景要有简单的了解 xff0c 对企业文化要有简单的认识 那么最关键的就属于问答环节 你问我答 对于这方面 xff0c 有人喜欢临时抱佛脚 但后来 xff0c 我深刻的认识到 xff0c
  • C语言| char[ ]的输出 和结束符 ‘\0‘

    输出char arr 逐个字符输入输出 c 整个字符串一次输入输出 s printf 34 61 61 61 61 61 61 start 61 61 61 61 61 61 n n 34 char arr 61 39 1 39 39 2
  • python中的isdigit()函数

    s为字符串 s isalnum 所有字符都是数字或者字母 s isalpha 所有字符都是字母 s isdigit 所有字符都是数字 s islower 所有字符都是小写 s isupper 所有字符都是大写 s istitle 所有单词都
  • 【WiFi模块ASP-ESP8266】理论讲解与串口助手验证(超详解)

    大家好 xff0c 我是小政 xff0c 一名自动化专业的在校学生 最近在学习利用opencv进行环境监测 xff0c 需要将获取到的数据通过通信协议传输给stm32 我选择使用wifi模块进行无线通信 xff0c 接下来我会将我从入门到掌
  • 【平衡小车制作】(二)电机驱动(超详解)

    大家好 xff0c 我是小政 本篇文章我将针对平衡小车中的电机控制进行详细的讲解 xff0c 驱动一个电机最重要的就是给它一个PWM信号 xff0c PWM的概念 如何驱动电机及通过改变PWM占空比进行电机调速我会在本篇文章中进行详解 让每
  • 【平衡小车制作】(五)位置式PID、直立环与速度环概念(超详解)

    大家好 xff0c 我是小政 本篇文章我将针对位置式PID算法 直立环 速度环等概念进行详细的讲解 xff0c 让每位小伙伴能够对这三个概念有更加清晰的理解 一 PID控制算法 PID控制 xff0c 即为对偏差进行比例 积分和微分控制 由
  • 【平衡小车制作】(七)串级PID调参及平衡成果展示(超详解)

    大家好 xff0c 我是小政 本篇文章我将针对PID调参进行详细的讲解 xff0c 让每位小伙伴能够对比例 积分 微分三个参数如何调节有更加清晰的理解 一 调参步骤 确立机械中值直立环 xff08 内环 xff09 Kp极性 Kp大小 Kd
  • 第七章:总线

    7 1总线概述 总线概述 总线是一组能为多个部件分时共享的公共信息传送线路 共享是指总线上可以挂接多个部件 xff0c 各个部件之间相互交换的信息都可以通过这组公共线路传送 xff1b 分时是指同一时刻总线上只能传送一个部件发送的信息 总线
  • Jetson 系统监视程序Jtop及Jetson基本信息查询

    开启后的界面如下 xff1a 安装Jetson的监视程序 xff1a 了解安装的Jetpack版本是多少 xff1f 了解 GPU 的使用率 xff1f 了解Jetson机器工作在什么模式下 xff1f 了解CPU用了几个 xff1f 散热
  • MVC设计模式

    MVC模式 xff08 Model View Controller xff09 是软件工程中的一种软件架构模式 xff0c 把软件系统分为三个基本部分 xff1a 模型 xff08 Model xff09 视图 xff08 View xff
  • Docker常用基础命令

    目录 1 docker version 查看版本信息 2 docker info 查看docker的一些信息 3 docker search 镜像搜索 4 docker pull 拉取镜像到本地 5 docker push 推送本地的镜像到
  • 硬件基础之变频器&逆变器

    变频器 amp 逆变器工作原理 基本概念直流产生方波思路正弦波产生原理正弦波产生之制作脉冲正弦波产生之滤波 amp 平均 基本概念 逆变器 xff1a 直流电 xff08 DC xff09 转变为交流电 xff08 AC xff09 变频器
  • 硬件基础之STM32最小系统

    STM32 复位电路时钟电路SWD下载电路BOOT电路RTC时钟电源电路 复位电路 STM32为低电平复位 xff0c 上电瞬间电流可视为交流 xff0c 电容导通 xff08 充电过程 xff0c NRST为低电平 xff0c 两个机械周

随机推荐

  • 数据结构常用的结构体定义

    一 顺序表结构体定义 xff1a typedef struct int data maxsize int length Sqlist 二 单链表结构体定义 xff1a typedef struct LNode int data struct
  • Linux基础入门(常用指令总结)

    相信大家对Linux都不陌生 xff0c 本篇文章就详细介绍一些常用的Linux命令 目录 前言 一 Linux是什么 xff1f 二 常用命令 1 cd 切换文件夹 2 ls 查看目录内容 3 mkdir 创建文件夹 4 touch 创建
  • 第7章:OFDM 信道估计与均衡(2)

    第7章 xff08 2 xff09 内容如下 xff1a 一 瑞利衰落信道介绍二 OFDM经过多径衰落信道的误码率分析三 总结 本文对应的完整可运行代码下载地址 xff1a 123kevin456 OFDM 一 瑞利衰落信道介绍 第 xff
  • 阿里云ECS服务器ubuntu18.04图形界面安装配置

    先说一下我用的ECS配置 服务器配置 xff1a 2 vCPU 4 GiB 系统镜像 xff1a ubuntu18 04 xff08 64位 xff09 本文的配置方法在 ubuntu 16 04 上一样适用 1 安装图形化界面 网上一大堆
  • java多线程-进程与线程基础-1

    基本概念 进程 是并发执行的程序在执行过程中分配和管理资源的基本单位 xff0c 是一个动态概念 xff0c 竟争计算机系统资源的基本单位 每一个进程都有一个自己的地址空 间 xff0c 即进程空间或 xff08 虚空间 xff09 进程空
  • 自由度的理解

    研究六轴机械臂的运动学前 xff0c 需要知道机械臂的自由度的定义 xff1a 物体上的任意一个参考点相对基座坐标系O可进行独立自由运动的数量 自由度的计算十分简洁 例如一个物体如正方体 xff0c 取其中心点作为参照点 xff0c 若该参
  • 匿名管道(pipe)和命名管道(mkfifo)

    匿名管道 xff08 pipe xff09 和命名管道 xff08 mkfifo xff09 进程间通信必须通过内核提供的通道 1 匿名管道 匿名管道通过打开的文件描述符来标识的 用于具有亲缘关系间进程之间的通信 int pipe xff0
  • 《Hadoop基础教程》读后感

    Hadoop一直是我想学习的技术 xff0c 正巧最近项目组要做电子商城 xff0c 我就开始研究Hadoop xff0c 虽然最后鉴定Hadoop不适用我们的项目 xff0c 但是我会继续研究下去 xff0c 技多不压身 Hadoop基础
  • WSL2安装CentOS7和Xfce4桌面

    由于工作和学习需要 xff0c 需要在WSL中安装和配置CentOS7系统 1 安装和升级WSL 安装和升级WSL方法可看微软官方教程 本人是将电脑中的WSL升级到了WSL2 2 下载安装CentOS镜像 首先需要下载CentOS7镜像 x
  • 安装MySQL5.7没设置密码解决方法

    我在WSL的CentOS7系统中安装MySQL5 7后发现全程都没设置密码 为了加强安全性 xff0c MySQL5 7为root用户随机生成了一个密码 xff0c 在error log中 xff0c 关于error log的位置 xff0
  • WSL的CentOS7报错Failed to get D-Bus connection: Operation not permitted解决办法

    WSL的CentOS7使用systemctl和service命令时报错Failed to get D Bus connection Operation not permitted 解决办法是更换systemctl文件 首先备份systemc
  • Go的常用设计模式

    好记性不如烂笔头 xff0c 学习编程的最好方式就是自己把代码动手敲一遍 1 策略模式 xff08 strategy xff09 在实际应用中 xff0c 我们对不同的场景要采取不同的应对措施 xff0c 也就是不同的策略 定义一个接口 x
  • Go语言使用golang-jwt/jwt/v4进行JWT鉴权

    文章目录 1 什么是JWT2 JWT的数据结构2 1 Header2 2 Payload2 3 Signature2 4 Base64URL 3使用JWT安装生成Token 参考 xff1a 最近写的项目中用到了JWT鉴权 xff0c 因此
  • 安装zsh并配置oh my zsh,让你的Linux终端也有爆爽体验

    安装zsh并配置oh my zsh 让你的Linux终端也有爆爽体验 xff01 Oh My Zsh 是基于 zsh 命令行的一个扩展工具集 xff0c 提供了丰富的扩展功能 下载安装zsh设置默认shell安装Oh My Zsh安装插件启
  • WSL2 Ubuntu20.04安装xfce4桌面和中文输入法

    目录 WSL2安装xfce4配置 zshrc安装VcXsrv启动桌面安装中文输入法 WSL2 首先需要开启wsl并且版本升级为wsl2 xff0c 然后microsoft store中搜索ubuntu xff0c 安装ubuntu20 04
  • Oracle11g下载地址

    网上链接大多挂了 xff0c 下面这个链接实测可以下载 Oracle11g下载地址
  • 关于元学习收集资料杂乱整理2021.9.11

    元学习论文 xff1a https github com rootlu MetaLearning Papers 应用于 NLP 领域的 Meta few shot 学习方法的最新论文列表 https github com ha lins M
  • C语言:字符数组的结束标志,输入输出,使用字符串处理函数等

    文章目录 字符串和字符串结束标志字符数组的输入输出用scanf函数输入 使用字符串处理函数puts函数 输出字符串的函数gets函数 输入strcat函数 字符串连接符strcpy和strncpy函数 字符串复制函数strcmp函数 字符串
  • fedora20/21 中文输入法 记录

    有了笔记后 xff0c 好久都不来写了 xff0c 罪过 这次是fedora下使用搜狗输入法 众所周知 xff0c Linux下的中文输入法是很难调教的 xff0c 可不像Windows下 xff0c 下载输入法然后安装就行 Linux下的
  • C语言-指针了解

    文章目录 一 什么是指针 xff1f 二 为什么要使用指针 xff1f 二 指针的声明与应用1 字符指针2 指针与数组2 1指针与数组的关系2 2指针数组2 3指针数组2 4数组名 VS 数组名2 5 数组传参 指针传参2 5 1 一维数组