4C语言基础 指针

指针是什么?

指针的定义

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的内存单元,可以说地址指向该内存单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元

这是官方对指针的定义,其实我们可以理解为:在内存中,内存被细分为一个个大小为一个字节的内存单元,每一个内存单元都有自己对应的地址。
在这里插入图片描述

我们可以将这些内存单元形象地看成一个个的房间,将内存单元(房间)对应的地址形象地看成房间的门牌号。而我们通过门牌号(地址)就可以唯一的找到其对应的房间(内存单元),即地址指向对应内存单元。所以说,可以将地址形象化的称为“指针”。

指针变量是用于存放地址的变量。(存放在指针中的值都将被当作地址处理)

#include<stdio.h>
int main()
{
	int a = 10;//在内存中开辟一块空间
	int* p = &a;//将a的地址取出,放到指针变量p中
	return 0;
}
 

总结:

  • 指针变量是用于存放地址的变量。(存放在指针中的值都将被当作地址处理)

指针的大小

对于32位的机器,即有32根地址线,因为每根地址线能产生正电(1)或负电(0),所以在32位的机器上能够产生的地址信号就是32个0/1组成的二进制序列:
在这里插入图片描述

一共 232 个地址。
同样的算法,在64位的机器上一共能产生 264 个不同的地址。
232 可以用32个bit位进行存储,而8个bit位等价于1个字节,所以在32位的平台下指针的大小为4个字节。
264 可以用64个bit位进行存储,所以在64位的平台下指针的大小为8个字节。
总结:

  • 在32位平台下指针的大小为4个字节,在64位平台下指针的大小为8个字节。

指针类型

指针有哪些类型?

我们知道,变量的类型有int,float,char等。那么指针有没有类型呢?回答是肯定的。
指针的定义方式是type+ *
char * 类型的指针存放的是char类型的变量地址;
int * 类型的指针存放的是int类型的变量地址;
float * 类型的指针存放的是float类型的变量地址等。

指针类型有什么意义?

1.指针±整数
若指针类型为int * 的指针+1,那么它将跳过4个字节的大小指向4个字节以后的内容:
在这里插入图片描述

若指针类型为char * 的指针+1,那么它只会跳过1个字节的大小指向下一个字节的内容,以此类推。
2.指针解引用
指针的类型决定了指针解引用的时候能够访问几个字节的内容。
若指针类型为int *,那么将它进行解引用操作,它将可以访问从指向位置开始向后4个字节的内容:
在这里插入图片描述

若指针类型为char *,那么将它进行解引用操作,它将可以访问从指向位置开始向后1个字节的内容,以此类推。
总结:

  • 指针的类型决定了指针向前或向后走一步有多大距离。
  • 指针的类型决定了指针在进行解引用操作时,能向后访问的空间大小。

代码演示:(偏移)

#include<stdio.h>



void test01()
{
const 	char * p1 = NULL;
	printf("%d\n", p1);
	printf("%d\n", p1 + 1);//+1偏移1个字节
	
	int * p2 = NULL;
	printf("%d\n", p2);
	printf("%d\n", p2 + 1);//+1偏移4个字节
	
	double * p3 = NULL;
	printf("%d\n", p3);
	printf("%d\n", p3 + 1);//+1偏移8个字节
}

int main()
{
	test01();
}
0
1
0
4
0
8

总结:vs下常见指针的偏移值分别为:

char :1个字节

int :4个字节

double :8个字节

数组指针:偏移整个数组大小

结构体指针:偏移整个结构体大小


以下是一段C语言代码,它演示了不同类型的指针加减整数时偏移量不同。这包括结构体指针、二维数组指针、一维数组指针、整型指针、字符指针和浮点型指针。

#include <stdio.h>

struct MyStruct {
    int a;
    double b;
    char c;
};

int main() {
    struct MyStruct s[2] = {{1, 2.0, 'a'}, {3, 4.0, 'b'}};
    struct MyStruct *sptr = s;

    int arr2d[2][2] = {{1, 2}, {3, 4}};
    int (*arr2dptr)[2] = arr2d;

    int arr[2] = {1, 2};
    int *intptr = arr;

    char str[2] = "ab";
    char *charptr = str;

    float f[2] = {1.0, 2.0};
    float *floatptr = f;

    printf("Address before incrementing struct pointer: %p\n", (void *)sptr);
    sptr++;
    printf("Address after incrementing struct pointer: %p\n", (void *)sptr);

    printf("Address before incrementing 2D array pointer: %p\n", (void *)arr2dptr);
    arr2dptr++;
    printf("Address after incrementing 2D array pointer: %p\n", (void *)arr2dptr);

    printf("Address before incrementing int pointer: %p\n", (void *)intptr);
    intptr++;
    printf("Address after incrementing int pointer: %p\n", (void *)intptr);

    printf("Address before incrementing char pointer: %p\n", (void *)charptr);
    charptr++;
    printf("Address after incrementing char pointer: %p\n", (void *)charptr);

    printf("Address before incrementing float pointer: %p\n", (void *)floatptr);
    floatptr++;
    printf("Address after incrementing float pointer: %p\n", (void *)floatptr);

    return 0;
}

这段代码首先定义了一个结构体 MyStruct,然后创建了一个指向该结构体数组的指针 sptr。接下来,它创建了一个二维数组 arr2d 和一个指向该二维数组的指针 arr2dptr,以及一个一维数组 arr 和一个指向该一维数组的指针 intptr。然后,它创建了一个字符串 str 和一个指向该字符串的指针 charptr,以及一个浮点数数组 f 和一个指向该浮点数数组的指针 floatptr

然后,它使用 printf 函数打印出每种类型的指针在加1之前和之后的地址。你会看到,不同类型的指针在加1之后的地址偏移量是不同的,这是因为不同类型的数据占用的字节数不同。希望这个代码对你有所帮助!

Address before incrementing struct pointer: 000000426f9ffba0
Address after incrementing struct pointer: 000000426f9ffbb8
    
Address before incrementing 2D array pointer: 000000426f9ffb90
Address after incrementing 2D array pointer: 000000426f9ffb98
    
Address before incrementing int pointer: 000000426f9ffb88
Address after incrementing int pointer: 000000426f9ffb8c
    
Address before incrementing char pointer: 000000426f9ffb85
Address after incrementing char pointer: 000000426f9ffb86
    
Address before incrementing float pointer: 000000426f9ffb7c
Address after incrementing float pointer: 000000426f9ffb80

在上述代码中,每种类型的指针在加1之后的地址偏移量取决于该类型的大小。具体来说:

  • 结构体指针 sptrMyStruct 结构体的大小是 sizeof(int) + sizeof(double) + sizeof(char),所以偏移量是这个值。
  • 二维数组指针 arr2dptr:它指向的是一个包含2个 int 的数组,所以偏移量是 2 * sizeof(int)
  • 整型指针 intptrint 的大小就是偏移量,即 sizeof(int)
  • 字符指针 charptrchar 的大小就是偏移量,即 sizeof(char),在大多数系统上,这个值是1。
  • 浮点型指针 floatptrfloat 的大小就是偏移量,即 sizeof(float)

代码演示(指针解引用时取出的字节数)

以下是一段C语言代码,它演示了不同类型的指针解引用时取出的字节数不同。这包括结构体指针、二维数组指针、一维数组指针、整型指针、字符指针和浮点型指针。

#include <stdio.h>

struct MyStruct {
    int a;
    double b;
    char c;
};

int main() {
    struct MyStruct s = {1, 2.0, 'a'};
    struct MyStruct *sptr = &s;

    int arr2d[2][2] = {{1, 2}, {3, 4}};
    int (*arr2dptr)[2] = arr2d;

    int arr[2] = {1, 2};
    int *intptr = arr;

    char str[2] = "ab";
    char *charptr = str;

    float f = 1.0;
    float *floatptr = &f;

    printf("Size of struct: %lu\n", sizeof(*sptr));
    printf("Size of 2D array element: %lu\n", sizeof(*arr2dptr));
    printf("Size of int: %lu\n", sizeof(*intptr));
    printf("Size of char: %lu\n", sizeof(*charptr));
    printf("Size of float: %lu\n", sizeof(*floatptr));

    return 0;
}

这段代码首先定义了一个结构体 MyStruct,然后创建了一个指向该结构体的指针 sptr。接下来,它创建了一个二维数组 arr2d 和一个指向该二维数组的指针 arr2dptr,以及一个一维数组 arr 和一个指向该一维数组的指针 intptr。然后,它创建了一个字符串 str 和一个指向该字符串的指针 charptr,以及一个浮点数 f 和一个指向该浮点数的指针 floatptr

最后,它使用 printfsizeof 函数打印出每种类型的指针解引用后的大小(即字节数)。这就是为什么不同类型的指针解引用时取出的字节数不同的原因。希望这个代码对你有所帮助!

Size of struct: 24
Size of 2D array element: 8
Size of int: 4
Size of char: 1
Size of float: 4

其他例子



// todo 字节序转换
#include <stdio.h>
#include <arpa/inet.h>
void printf_bin(int num) // 这个函数将整形变量以二进制的形式打印出来
{
    int i, j, k;
    unsigned char *p = (unsigned char *)&num + 3; // p先指向num后面第3个字节的地址,即num的最高位字节地址

    for (i = 0; i < 4; i++) // 依次处理4个字节(32位)
    {
        j = *(p - i);                // 取每个字节的首地址,从高位字节到低位字节,即p p-1 p-2 p-3地址处
        for (int k = 7; k >= 0; k--) // 处理每个字节的8个位,注意字节内部的二进制数是按照人的习惯存储!
        {
            if (j & (1 << k)) // 1左移k位,与单前的字节内容j进行或运算,如k=7时,00000000&10000000=0 ->该字节的最高位为0
                printf("1");
            else
                printf("0");
        }
        printf(" "); // 每8位加个空格,方便查看
    }
    printf("\r\n");
}

int main()
{

    char buf[4] = {192, 168, 1, 2}; // 32位

    // todo1 将 4字节(32位)的数据存放在 num容器(int 类型, 32位)中
    unsigned int num = *(unsigned int *)buf; // int*把buf(char类型的数组首地址强转为int*类型的地址),
                                             // 再*(解引用)取出四个字节的数据,而int 类型刚好是4字节,就能存放这四字节数据

    // 你可以把 int num 当成是定义了一个能存放32位数据的容器,只是这32位存放的是
    // 192.168.1.2 的用二进制(01)表示的情况

    printf_bin(num);

    printf("%u\n", num); //%u用于打印 unsigned int .
    // 打印结果 33663168 . 这么大是因为 他不会每八位隔断,每八位分别做二进制转换
    //(像把 00000010 00000001 10101000 11000000)隔断为 192.168.1.2 而是
    // 直接将这个32位的数作为整体进行二进制转换
    printf("================");
    int n1 = 33663168; // 实际上这个十进制数用二进制的表示就是 00000010 00000001 10101000 11000000
    printf_bin(n1);
    // htol() 函数的作用是将一个32位数从主机字节顺序转换成网络字节顺序。
    unsigned int sum = htonl(num);
    printf_bin(sum); // 11000000 10101000 00000001 00000010  和 num中的二进制位是相反的
}


假设 num 是一个 int 类型的变量,占用4个字节,那么 &num 就是这个变量的地址。由于 unsigned char * 是一个字节指针,所以 p 指向 num 的第一个字节。当你加上3 (+ 3),p 就会指向 num 的第四个字节。

如果你的系统是小端模式,那么 num 的最高有效字节就会被存储在最高的地址,也就是 p 所指向的位置。如果你的系统是大端模式,那么 p 就会指向 num 的最低有效字节。

00000010 00000001 10101000 11000000 
33663168
================
00000010 00000001 10101000 11000000 
11000000 10101000 00000001 00000010 

野指针

概念:野指针就是指向位置是不可知的(随机的、不正确的、没有明确限制的)指针。

野指针的成因

1.指针未初始化

#include<stdio.h>
int main()
{
	int* p;
	*p = 10;
	return 0;
}
 

局部指针变量p未初始化,默认为随机值,所以这个时候的p就是野指针。
2.指针越界访问

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i < 11; i++)
	{
		*p++ = i;
	}
	return 0;
}
  12

当指针指向的范围超出arr数组时,p就是野指针。
3.指针指向的空间被释放

#include<stdio.h>
int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test();
	return 0;
}
  

指针变量p得到地址后,地址指向的空间已经释放了,所以这个时候的p就是野指针。(局部变量出了自己的作用域就被释放了)

如何避免野指针

1.指针初始化
当指针明确知道要存放某一变量地址时,在创建指针变量时就存放该变量地址。
当不知道指针将要用于存放哪一变量地址时,在创建指针变量时应置为空指针(NULL)。

#include<stdio.h>
int main()
{
	int a = 10;
	int* p1 = &a;//明确知道存放某一地址
	int* p2 = NULL;//不知道存放哪一地址时置为空指针
	return 0;
}
 

2.小心指针越界
3.指针指向的空间被释放后及时置为NULL
3.使用指针之前检查有效性
在使用指针之前需确保其不是空指针,因为空指针指向的空间是无法访问的。

指针运算

指针±整数

#include<stdio.h>
int main()
{
	int arr[5] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	return 0;
}
  12

指针-指针

指针-指针的绝对值是是两个指针之间的元素个数。

int my_strlen(char* p)
{
	char* pc = p;
	while (*p != '\0')
		p++;
	return p - pc;
}
 

strlen函数的模拟实现就可以运用指针-指针的代码实现。

指针的关系运算

指针的关系运算,即指针之间的大小比较。
我们如果要将一个数组中的元素全部置0,可以有两种方法。
第一种:从前向后置0

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

最终指向数组最后一个元素后面的那个内存位置的指针将与&arr[4]比较,不满足条件,于是结束循环。

第二种:从后向前置0

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

最终指向第一个元素之前的那个内存位置的指针将与&arr[0]比较,不满足条件,于是结束循环。

这两种方法在绝大部分编译器下均能将arr数组中的元素置0,但是我们要尽量使用第一种方法,因为标准并不保证第二种方法可行。
标准规定:
允许数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

二级指针

我们知道,指针变量是用于存放地址的变量。但是指针变量也是变量,是变量就有地址,那么存放指针变量的地址的变量是什么呢?

其实,存放普通变量的地址的指针叫一级指针,存放一级指针变量的地址的指针叫二级指针,存放二级指针变量地址的指针叫三级指针,以此类推。

#include<stdio.h>
int main()
{
	int a = 10;
	int* p1 = &a;
	int** p2 = &p1;
	return 0;
}
 

在这里,我们用一级指针p1存放了普通常量a的地址,用二级指针p2存放了一级指针p1的地址。
这时如果我们要得到a的值,就有两种方法:
方法一:对一级指针p1进行一次解引用操作即可得到a的值,即*p1。
方法二:对二级指针p2进行一次解引用操作即可得到p1的值,而p1的值就是a的地址,所以再对p2进行一次解引用操作即可得到a的值,也就是对二级指针p2进行两次解引用操作即可得到a的值,即**p2。


字符指针

我们知道,在指针的类型中有一种指针类型叫字符指针char * 。
字符指针的一般使用方法为:

#include<stdio.h>
int main()
{
	char ch = 'w';
	char* p = &ch;
	return 0;
}

代码中,将字符变量ch的地址存放在了字符指针p中。

其实,字符指针还有另一种使用方式:

#include<stdio.h>
int main()
{
	char* p = "hello csdn.";
	printf("%c\n", *p);//打印字符'h'
	printf("%s\n", p);//打印字符串"hello csdn."
	return 0;
}

代码中,字符指针p中存放的并非字符串"hello csdn.",字符指针p中存放的是字符串"hello csdn.“的首元素地址,即字符’h’的地址。
所以,当对字符指针p进行解引用操作并以字符的形式打印时只能打印字符’h’。我们知道,打印一个字符串只需要提供字符串的首元素地址即可,既然字符指针p中存放的是字符串的首元素地址,那么我们只要提供p(字符串首地址)并以字符串的形式打印,便可以打印字符串"hello csdn.”。
注意:代码中的字符串"hello csdn."是一个常量字符串。

这里有一道题目,可以帮助我们更好的理解字符指针和常量字符串:

#include <stdio.h>
int main()
{
	char str1[] = "hello csdn.";
	char str2[] = "hello csdn.";
	char *str3 = "hello csdn.";
	char *str4 = "hello csdn.";
	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;
}
 

题目最后打印的结果是:

str1 and str2 are not same
str3 and str4 are same

题目中str1和str2是两个字符数组,比较str1和str2时,相当于比较数组str1和数组str2的首元素地址,而str1与str2是两个不同的字符数组,创建数组str1和数组str2是会开辟两块不同的空间,它们的首元素地址当然不同。
在这里插入图片描述

而str3和str4是两个字符指针,它们指向的都是常量字符串"hello csdn."的首元素地址,所以str3和str4指向的是同一个地方。
在这里插入图片描述

注意:常量字符串与普通字符串最大的区别是,常量字符串是不可被修改的字符串,既然不能被修改,那么在内存中没有必要存放两个一模一样的字符串,所以在内存中相同的常量字符串只有一个。

指针数组

我们已经知道了整型数组、字符数组等。整型数组是用于存放整型的数组,字符数组是用于存放字符的数组。

int arr1[4];
char arr2[5];

数组arr1包含4个元素,每个元素的类型是整型;数组arr2包含5个元素,每个元素的类型是字符型。
在这里插入图片描述

指针数组也是数组,是用于存放指针的数组。

int* arr3[5];

数组arr3包含5个元素,每个元素是一个一级整型指针。
在这里插入图片描述

以此类推:

char* arr4[10];//数组arr4包含10个元素,每个元素是一个一级字符型指针。
char** arr5[5];//数组arr5包含5个元素,每个元素是一个二级字符型指针。

数组arr4包含10个元素,每个元素是一个一级字符型指针;数组arr5包含5个元素,每个元素是一个二级字符型指针。

数组指针

数组指针的定义

我们已经知道了,整型指针是指向整型的指针,字符指针是指向字符的指针,那么数组指针应该就是指向数组的指针了。
整型指针和字符指针,在使用时只需取出某整型/字符型的数据的地址,并将地址存入整型/字符型指针即可。

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//取出a的地址存入整型指针中
	char ch = 'w';
	char* pc = &ch;//取出ch的地址存入字符型指针中
	return 0;
}

数组指针也是一样,我们只需取出数组的地址,并将其存入数组指针即可。

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
	//&arr - 数组的地址
	return 0;
}

那么数组指针的指针类型是如何写出来的呢?

首先我们应该知道的是:
1. [ ]的优先级要高于 * 。
2. 一个变量除去了变量名,便是它的变量类型。
比如:

int a = 10;//除去变量名a,变量类型为int
char ch = 'w';//除去变量名ch,变量类型为char
int* p = NULL;//除去变量名p,变量类型为int*

数组也可以这样理解:

int arr[10] = { 0 };//除去变量名arr,变量类型为int [10]
int arr[3][4] = { 0 };//除去变量名arr,变量类型为int [3][4]
int* arr[10] = { 0 };//除去变量名arr,变量类型为int* [10]

数组的变量类型说明了数组的元素个数和每个元素的类型:
在这里插入图片描述

3.一个指针变量除去了变量名和 * ,便是指针指向的内容的类型。
比如:

int a = 10;
int* p = &a;//除去变量名(p)和*,便是P指向的内容(a)的类型->int
char ch = 'w';
char* pc = &ch;//除去变量名(pc)和*,便是pc指向的内容(ch)的类型->char

接下来我们就可以来写数组指针的指针类型了:

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
	//&arr - 数组的地址
	return 0;
}

首先p是一个指针,所以p必须要先与 * 结合,而[ ]的优先级要高于 * ,所以我们要加上( )以便让p与 * 先结合。
指针p指向的内容,即数组类型是int [10],所以数组指针p就变成了int(*p)[10]。
去掉变量名p后,便是该数组指针的变量类型int( * )[10]。

在这里插入图片描述

如果我们不加( ),那么就变成了int *p[10],因为[ ]的优先级要高于 * ,所以p先与[ ]结合,这时p就变成了一个数组,而数组中每个元素的的类型是int * ,这就是前面说到的指针数组。

在这里插入图片描述

&数组名 VS 数组名

对于一个数组的数组名,它什么时候代表数组首元素的地址,什么时候又代表整个数组的地址,这一直是很多人的疑惑。在这里我给出大家准确的答案:
数组名代表整个数组的地址的情况其实只有两种:

  1. &数组名。
  2. 数组名单独放在sizeof内部,即sizeof(数组名)。

除此之外,所有的数组名都是数组首元素地址。
比如:

int arr[5] = { 1, 2, 3, 4, 5 };

对于该数组arr,只有以下两种情况数组名代表整个数组的地址:

	&arr;
	sizeof(arr);//arr单独放在sizeof内部

除此之外,所以的arr都代表数组首元素地址,即1的地址。

将其与指针联系起来:

#include<stdio.h>
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	int* p1 = arr;//数组首元素的地址
	int(*p2)[5] = &arr;//数组的地址
	printf("%p\n", p1);
	printf("%p\n", p2);

	printf("%p\n", p1+1);
	printf("%p\n", p2+1);
	return 0;
}
 

因为代码中的arr是数组首元素地址,所以要用int * 的指针接收。而&arr是整个数组的地址,所以要用数组指针进行接收。
虽然一个是数组首元素地址,一个是数组的地址,但是它们存放的都是数组的起始位置的地址,所以将p1和p2以地址的形式打印出来发现它们的值一样。
数组首元素地址和数组的地址的区别在于,数组首元素地址+1只能跳过一个元素指向下一个元素,而数组的地址+1能跳过整个数组指向数组后面的内存空间。
在这里插入图片描述

数组指针的使用

数组指针有一个简单的使用案例,那就是打印二维数组:

#include<stdio.h>
void print(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)//行数
	{
		int j = 0;
		for (j = 0; j < col; j++)//列数
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");//打印完一行后,换行
	}
}
int main()
{
	int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 2, 3, 4, 5, 6 }, { 3, 4, 5, 6, 7 } };
	print(arr, 3, 5);//传入二维数组名,即二维数组首元素地址,即二维数组第一行的地址
	return 0;
}
 

在这里我们打印一个三行五列的二维数组。传参时我们传入二维数组的数组名,明确打印的起始位置;传入行数和列数,明确打印的数据范围。
通过上面对&数组名和数组名的认识,我们知道了这里传入的数组名代表的是二维数组的首元素地址,而二维数组的首元素第一行的元素,即传入的是一维数组的地址,所以我们必须用数组指针进行接收。
打印时,通过表达式 * (*(p+i)+j ) 锁定打印目标:
在这里插入图片描述

数组参数、指针参数

一维数组传参

#include<stdio.h>
void test1(int arr[10])//数组接收
{}
void test1(int *arr)//指针接收
{}
void test2(int *arr[20])//数组接收
{}
void test2(int **arr)//指针接收
{}
int main()
{
	int arr1[10] = { 0 };//整型数组
	int *arr2[20] = { 0 };//整型指针数组
	test1(arr1);
	test2(arr2);
}

整型数组:
当向函数传入整型数组的数组名时,我们有以下几种参数可供接收:

  1. 数组传参数组接收,我们传入的是整型数组,那我们就用整型数组接收。
  2. 传入的数组名本质上是数组首元素地址,所以我们可以用指针接收。数组的元素类型是整型,我们接收整型元素的地址用int * 的指针即可。

整型指针数组:
当向函数传入整型指针数组的数组名时,我们有以下几种参数可供接收:

  1. 数组传参数组接收,我们传入的是整型指针数组,那我们就用整型指针数组接收。
  2. 指针接收,数组的元素是int * 类型的,我们接收int * 类型元素的地址用二级指针int ** 即可。

注意:一维数组传参,函数形参设计时[ ]内的数字可省略。

二维数组传参

#include<stdio.h>
void test(int arr[][5])//数组接收
{}
void test(int(*arr)[5])//指针接收
{}
int main()
{
	int arr[3][5] = { 0 };//二维数组
	test(arr);
}

当向函数传入二维数组的数组名时,我们有以下几种参数可供接收:

  1. 二维数组传参二维数组接收。
  2. 指针接收,二维数组的首元素是二维数组第一行的地址,即一维数组的地址,我们用数组指针接收即可。

注意:二维数组传参,函数形参的设计只能省略第一个[ ]内的数字。

一级指针传参

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

当我们传入的参数为一级指针时,我们可以用一级指针的形参对其进行接收,那么当函数形参为一级指针的时候,我们可以传入什么样的参数呢?

#include<stdio.h>
void test(int* p)
{}
int main()
{
	int a = 10;
	test(&a);//可以传入变量的地址
	int* p = &a;
	test(p);//可以传入一级指针
	int arr[10] = { 0 };
	test(arr);//可以传入一维数组名
	//...
	return 0;
}
  

总而言之,只要传入的表达式最终的类型是一级指针类型即可传入。

二级指针传参

#include<stdio.h>
void test(int** p)//二级指针接收
{}
int main()
{
	int a = 10;
	int* pa = &a;
	int** paa = &pa;
	test(paa);//二级指针
	return 0;
}
 

当我们传入的参数为二级指针时,我们可以用二级指针的形参对其进行接收,那么当函数形参为二级指针的时候,我们可以传入什么样的参数呢?

#include<stdio.h>
void test(int** p)
{}
int main()
{
	int a = 10;
	int* pa = &a;
	test(&pa);//可以传入一级指针的地址
	int** paa = &pa;
	test(paa);//可以传入二级指针
	int* arr[10];
	test(arr);//可以传入一级指针数组的数组名
	//...
	return 0;
}
 

总而言之,只要传入的表达式最终的类型是二级指针类型即可传入。


函数指针

函数指针的定义

函数指针和我们在对指针的详细认识(二)中学习的数组指针非常相似。
我们知道,整型指针是指向整型的指针,数组指针是指向数组的指针,其实,函数指针就是指向函数的指针
和学习数组指针一样,学习函数指针我们也需要知道三点:

  1. ( )的优先级要高于 * 。
  2. 一个变量除去了变量名,便是它的变量类型。
  3. 一个指针变量除去了变量名和 * ,便是指针指向的内容的类型。

举个例子:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*p)(int, int) = &Add;//取出函数的地址放在函数指针p中
	return 0;
}
12345678910

那么,函数指针p的类型我们是如何创建的呢?
首先,p是一个指针,所以必须先与 * 结合,而( )的优先级高于 * ,所以我们要把 * 和p用括号括起来,让它们先结合。
指针p指向的内容,即函数Add的类型是int (int,int),所以函数指针p就变成了int(*p)(int,int)。
去掉变量名p后,便是该函数指针的变量类型int( * )(int,int)。
在这里插入图片描述

函数指针的使用

知道了如何创建函数指针,那么函数指针应该如何使用呢?
1.函数指针的赋值
对于数组来说,数组名和&数组名它们代表的意义不同,数组名代表的是数组首元素地址,而&数组名代表的是整个数组的地址。
但是对于函数来说,函数名和&函数名它们代表的意义却是相同的,它们都代表函数的地址(毕竟你也没有听说过函数有首元素这个说法吧)。
所以,当我们对函数指针赋值时可以赋值为&函数名,也可以赋值为函数名。

	int(*p)(int, int) = &Add;
	int(*p)(int, int) = Add;
12

2.通过函数指针调用函数
方法一:我们知道,函数指针存放的是函数的地址,那么我们将函数指针进行解引用操作,便能找到该函数了,于是就可以通过函数指针调用该函数。

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 20;
	int(*p)(int, int) = &Add;
	int ret = (*p)(a, b);//解引用找到该函数
	printf("%d\n", ret);
	return 0;
}

我们可以理解为, * 和&是两个相反的操作符,像正号(+)和负号(-)一样,一个 * 操作符可以抵消一个&操作符。
在这里插入图片描述

方法二:我们在函数指针赋值中说到,函数名和&函数名都代表函数的地址,我们可以赋值时直接赋值函数名,那么通过函数指针调用函数的时候我们就可以不用解引用操作符就能找到函数了。

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 20;
	int(*p)(int, int) = Add;
	int ret = p(a, b);//不用解引用
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

函数指针数组

函数指针数组的定义

我们知道,数组是一个存放相同类型数据的空间,我们已经认识了指针数组,比如:

	int* arr[10];//数组arr有10个元素,每个元素的类型是int*
1

那如果要将一系列相同类型的函数指针存放到一个数组中,那么这个数组就叫做函数指针数组,比如:

	int(*pArr[10])(int, int);
	//数组pArr有10个元素,每个元素的类型是int(*)(int,int)

函数指针数组的创建只需在函数指针创建的基础上加上[ ]即可。
比如,你要创建一个函数指针数组,这个数组中存放的函数指针的类型均为int(*)(int,int),如果你要创建一个函数指针为该类型,那么该函数指针的写法为int(*p)(int,int),现在你要创建一个存放该指针类型的数组,只需在变量名的后面加上[ ]即可,int(*pArr[10])(int,int)。

函数指针数组的使用 - 模拟计算器

函数指针数组一个很好的运用场景,就是计算机的模拟实现:

#include<stdio.h>
void menu()
{
	printf("|-----------------------|\n");
	printf("|     1.Add   2.Sub     |\n");
	printf("|     3.Mul   4.Div     |\n");
	printf("|        0.exit         |\n");
	printf("|-----------------------|\n");
}//菜单
double Add(double x, double y)
{
	return x + y;
}//加法函数
double Sub(double x, double y)
{
	return x - y;
}//减法函数
double Mul(double x, double y)
{
	return x*y;
}//乘法函数
double Div(double x, double y)
{
	return x / y;
}//除法函数
int main()
{
	int input = 0;
	double x = 0;//第一个操作数
	double y = 0;//第二个操作数
	double ret = 0;//运算结果
	double(*pArr[])(double, double) = { 0, Add, Sub, Mul, Div };
	//函数指针数组-转移表
	int sz = sizeof(pArr) / sizeof(pArr[0]);//计算数组的大小
	do
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);
		if (input == 0)
			printf("退出程序\n");
		else if (input > 0 && input < sz)
		{
			printf("请输入两个操作数:>");
			scanf("%lf %lf", &x, &y);
			ret = pArr[input](x, y);
			printf("ret=%lf\n", ret);
		}
		else
			printf("选择错误,请重新选择!\n");
	} while (input);//当input不为0时循环继续
	return 0;
}
 

代码中,函数指针数组存放的是一系列参数和返回类型相同的函数名,即函数指针。将0放在该函数指针数组的第一位是为了让用户输入的数字input与对应的函数指针下标相对应。
该代码若不使用函数指针数组,而选择使用一系列的switch分支语句当然也能达到想要的效果,但会使代码出现许多重复内容,而且当以后需要增加该计算机功能时又需要增加一个case语句,而使用函数指针数组,当你想要增加计算机功能时只需在数组中加入一个函数名即可。

指向函数指针数组的指针

既然存在函数指针数组,那么必然存在指向函数指针数组的指针。

	int(*p)(int, int);
	//函数指针
	int(*pArr[5])(int, int);
	//函数指针数组
	int(*(*pa)[5])(int, int) = &pArr;
	//指向函数指针数组的指针
123456

那指向函数指针数组的指针的类型是如何写的呢?
在这里插入图片描述

所以pa就是一个指向函数指针数组的指针,该函数指针数组中每个元素类型是int(*)(int, int)。

回调函数

回调函数的定义

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
举个简单的例子:

#include<stdio.h>
void test1()
{
	printf("hello\n");
}
void test2(void(*p)())
{
	p(); //指针p被用来调用其所指向的函数
}
int main()
{
	test2(test1);//将test1函数的地址传递给test2
	return 0;
}
 

在该代码中test1函数不是由该函数的实现方直接调用,而是将其地址传递给test2函数,在test2函数中通过函数指针间接调用了test1函数,那么函数test1就被称为回调函数。

回调函数的使用 - qsort函数

其实回调函数并不是很难见到,在用于快速排序的库函数qsort中便运用了回调函数。

void qsort(void*base,size_t num,size_t width,int(*compare)(const void*e1,const void*e2));

qsort函数的第一个参数是待排序的内容的起始位置;第二个参数是从起始位置开始,待排序的元素个数;第三个参数是待排序的每个元素的大小,单位是字节;第四个参数是一个函数指针。qsort函数的返回类型为void。
qsort函数的第四个参数是一个函数指针,该函数指针指向的函数的两个参数的参数类型均为const void*,返回类型为int。当参数e1小于参数e2时返回小于0的数;当参数e1大于参数e2时返回大于0的数;当参数e1等于参数e2时返回0。

列如,我们要排一个整型数组:

#include<stdio.h>
int compare(const void* e1, const void* e2)
{
	return *((int*)e1) - *((int*)e2);
}//自定义的比较函数
int main()
{
	int arr[] = { 2, 5, 1, 8, 6, 10, 9, 3, 5, 4 };
	int sz = sizeof(arr) / sizeof(arr[0]);//元素个数
	qsort(arr, sz, 4, compare);//用qsort函数将arr数组排序
	return 0;
}
 

最终arr数组将被排为升序。

注意:qsort函数默认将待排序的内容排为升序,如果我们要排为降序可将自定义的比较函数的两个形参的位置互换一下即可。

在qsort函数中我们传入了一个函数指针,最终qsort函数会在其内部通过该函数指针调用该函数,那么我们的这个自定义比较函数就被称为回调函数。


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

#include <stdio.h>

//qsort函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2) {
  return (*(int *)p1 - *(int *) p2);//转换为int *之后再解引用
}

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

使用回调函数,模拟实现qsort(采用冒泡的方式)。
注意:这里第一次使用 void* 的指针,讲解 void* 的作用。
void* :可以用任意类型的指针对 void 指针赋值或者强制转换为任意类型的指针。

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

指针和数组试题解析

数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。{sizeof()括号中仅仅有数组名时}
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

地址就是指针变量,指针变量就是地址,对地址加减整数,就是对指针变量加减整数,

整型数组字节数

int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));  // 16
	printf("%d\n", sizeof(a + 0));  //第一个元素的地址(指针),指针加减整数,即偏移sizeof(指针类型)字节,但还是a 还是表示指向偏移后位置元素的指针
	printf("%d\n", sizeof(*a)); // 对首元素地址进行解引用,取出该位置元素
	printf("%d\n", sizeof(a + 1)); //a 是首元素地址(指针).指针加减整数, 偏移sizeof(指针类型)字节,但还是a 还是表示指向偏移后位置元素的指针
	printf("%d\n", sizeof(a[1])); // a[1] 是第二个元素
	printf("%d\n", sizeof(&a)); // 整个数组的地址(指针),也是指针,是指针大小
	printf("%d\n", sizeof(*&a));  // &a 是指向 整个数组的地址(指针)(即数组类型的指针) ,对指针解引用,取出的字节也是 整个数组的大小. 
	printf("%d\n", sizeof(&a + 1));  // &a 是指向 整个数组的地址(指针)(即数组类型的指针),对该指针加减整数,偏移的大小是 sizeof(指针类型的)字节,但还是a 还是表示指向偏移后位置元素的指针
	printf("%d\n", sizeof(&a[0])); // 对第一个元素取地址, 相当于整型指针,是指针大小
	printf("%d\n", sizeof(&a[0] + 1)); // &a 是 对第一个元素取地址, 相当于整型指针,指针加1, 指针加减整数, 偏移sizeof(指针类型)字节,但还是a 还是表示指向偏移后位置元素的指针(地址)

  1. printf("%d\n", sizeof(a)); // 16sizeof(a)返回数组a的总字节大小。因为a是一个包含4个int类型元素的数组,每个int类型元素在大多数系统上占4字节,所以sizeof(a)返回16。
  2. printf("%d\n", sizeof(a + 0));a是一个数组,当用在表达式中时,它会被转换为指向其第一个元素的指针。所以a + 0是一个指针,sizeof(a + 0)返回的是指针的大小。在32位系统上,这将是4,而在64位系统上,这将是8。
  3. printf("%d\n", sizeof(*a));*a解引用了指向数组第一个元素的指针,所以它的值是数组的第一个元素,即1。因为这是一个int类型的元素,所以sizeof(*a)返回的是int的大小,即4。
  4. printf("%d\n", sizeof(a + 1));:同样,a + 1也是一个指针,所以sizeof(a + 1)返回的是指针的大小。
  5. printf("%d\n", sizeof(a[1]));a[1]是数组的第二个元素,是一个int类型的元素,所以sizeof(a[1])返回的是int的大小,即4。
  6. printf("%d\n", sizeof(&a));&a是一个指向数组的指针,所以sizeof(&a)返回的是指针的大小。
  7. printf("%d\n", sizeof(*&a)); // 16*&a是对指向数组的指针的解引用,所以它的值是整个数组。因此,sizeof(*&a)返回的是数组的总字节大小,即16。
  8. printf("%d\n", sizeof(&a + 1));&a + 1是一个指针,所以sizeof(&a + 1)返回的是指针的大小。
  9. printf("%d\n", sizeof(&a[0]));&a[0]是一个指向数组第一个元素的指针,所以sizeof(&a[0])返回的是指针的大小。
  10. printf("%d\n", sizeof(&a[0] + 1));&a[0] + 1也是一个指针,所以sizeof(&a[0] + 1)返回的是指针的大小。

字符数组长度和字节数


char arr[] = { 'a','b','c','d','e','f' };  //是字符数组,但不是字符串,因为没有 '\0'
printf("%d\n", sizeof(arr)); // 整个数组的大小
printf("%d\n", sizeof(arr + 0)); // 首元素地址 +1 , 指针的大小
printf("%d\n", sizeof(*arr));  // 首元素地址,解引用之后是元素的大小
printf("%d\n", sizeof(arr[1])); //1
printf("%d\n", sizeof(&arr));  // 整个数组的地址(指针)的大小,也是指针的大小
printf("%d\n", sizeof(&arr + 1));  //   整个数组的地址(指针)偏移  {是指向 整个数组的地址(指针)(即数组类型的指针)},偏移sizeof(指针类型)字节,但还是arr 还是表示指向偏移后位置元素的指针
printf("%d\n", sizeof(&arr[0] + 1));  // 首元素地址(指针)偏移,指向4个字节之后的元素,但还是指针



strlen 只会在找到 '\0'才会停止




printf("%d\n", strlen(arr));  // 随机值
printf("%d\n", strlen(arr + 0));  // 随机值
printf("%d\n", strlen(arr)); //随机值
printf("%d\n", strlen(arr+0));//随机值
printf("%d\n", strlen(*arr));//只接受字符串的起始位置,报错
printf("%d\n", strlen(arr[1]));//报错
printf("%d\n", strlen(&arr));//整个数组的地址(一个指向数组的指针 类型)  ,但strlen 接收 指向字符串类型的指针,(字符数组可以看成字符串,但是没有'\0')
  1. printf("%d\n", sizeof(arr)); // 6sizeof(arr)返回数组arr的总字节大小。因为arr是一个包含6个char类型元素的数组,每个char类型元素占1字节,所以sizeof(arr)返回6。
  2. printf("%d\n", sizeof(arr + 0)); // 4arr是一个数组,当用在表达式中时,它会被转换为指向其第一个元素的指针。所以arr + 0是一个指针,sizeof(arr + 0)返回的是指针的大小。在32位系统上,这将是4,而在64位系统上,这将是8。
  3. printf("%d\n", sizeof(*arr)); // 1*arr解引用了指向数组第一个元素的指针,所以它的值是数组的第一个元素,即’a’。因为这是一个char类型的元素,所以sizeof(*arr)返回的是char的大小,即1。
  4. printf("%d\n", sizeof(arr[1])); //1arr[1]是数组的第二个元素,是一个char类型的元素,所以sizeof(arr[1])返回的是char的大小,即1。
  5. printf("%d\n", sizeof(&arr)); // 4&arr是一个指向数组的指针,所以sizeof(&arr)返回的是指针的大小。
  6. printf("%d\n", sizeof(&arr + 1)); // 4&arr + 1是一个指针,所以sizeof(&arr + 1)返回的是指针的大小。
  7. printf("%d\n", sizeof(&arr[0] + 1)); // 4&arr[0] + 1也是一个指针,所以sizeof(&arr[0] + 1)返回的是指针的大小。
  8. printf("%d\n", strlen(arr)); // 19strlen(arr)返回的是字符串arr的长度。但是,你的数组arr没有包含空字符’\0’,这是字符串结束的标志。所以,strlen(arr)实际上是在计算从数组arr的开始位置到它找到的第一个空字符之间的字符数。这个结果是不确定的,因为它取决于在内存中紧接着arr的位置是否恰好有一个空字符。
  9. printf("%d\n", strlen(arr + 0)); // 19:这和上一行的解释相同。arr + 0是一个指向数组第一个元素的指针,所以strlen(arr + 0)strlen(arr)的结果是相同的。
  10. printf("%d\n", strlen(arr));:这和第8行的解释相同。
  11. printf("%d\n", strlen(arr+0));:这和第9行的解释相同。
  12. printf("%d\n", strlen(*arr));:这里有一个问题。strlen函数的参数应该是一个指向字符的指针,但*arr是一个字符,不是一个指针。所以这行代码是错误的,会导致编译错误。
  13. printf("%d\n", strlen(arr[1]));:这和第12行的解释相同。arr[1]是一个字符,不是一个指针。所以这行代码是错误的,会导致编译错误。
  14. printf("%d\n", strlen(&arr));:这里也有一个问题。strlen函数的参数应该是一个指向字符的指针,但&arr是一个指向数组的指针,不是一个指向字符的指针。所以这行代码是错误的,会导致编译错误。

字符串长度和字节数

	char *p = "abcdef";
	printf("%d\n", sizeof(p));  // 4
	printf("%d\n", sizeof(p + 1));  // 4  p+1 作为char 类型的指针,移动到b字符位置
	printf("%d\n", sizeof(*p));  //1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));  // 4   二级指针的大小,也是指针的大小
	printf("%d\n", sizeof(&p + 1)); // 4
	printf("%d\n", sizeof(&p[0] + 1)); // 4
	printf("%d\n", strlen(p));  // 6
	printf("%d\n", strlen(p + 1)); // 5
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));

这是每行代码的解释:

  1. printf("%d\n", sizeof(p)); // 4sizeof(p)返回的是指针p的大小。在32位系统上,这将是4,而在64位系统上,这将是8。
  2. printf("%d\n", sizeof(p + 1)); // 4p + 1是一个指针,所以sizeof(p + 1)返回的是指针的大小。
  3. printf("%d\n", sizeof(*p)); //1*p解引用了指向字符串第一个字符的指针,所以它的值是字符串的第一个字符,即’a’。因为这是一个char类型的元素,所以sizeof(*p)返回的是char的大小,即1。
  4. printf("%d\n", sizeof(p[0]));//1p[0]是字符串的第一个字符,是一个char类型的元素,所以sizeof(p[0])返回的是char的大小,即1。
  5. printf("%d\n", sizeof(&p)); // 4&p是一个指向指针的指针,所以sizeof(&p)返回的是指针的大小。
  6. printf("%d\n", sizeof(&p + 1)); // 4&p + 1是一个指针,所以sizeof(&p + 1)返回的是指针的大小。
  7. printf("%d\n", sizeof(&p[0] + 1)); // 4&p[0] + 1也是一个指针,所以sizeof(&p[0] + 1)返回的是指针的大小。
  8. printf("%d\n", strlen(p)); // 6strlen(p)返回的是字符串p的长度。字符串p有6个字符,所以strlen(p)返回6。
  9. printf("%d\n", strlen(p + 1)); // 5p + 1是一个指向字符串第二个字符的指针,所以strlen(p + 1)返回的是从字符串的第二个字符开始的子字符串的长度。这个子字符串有5个字符,所以strlen(p + 1)返回5。
  10. printf("%d\n", strlen(*p));:这里有一个问题。strlen函数的参数应该是一个指向字符的指针,但*p是一个字符,不是一个指针。所以这行代码是错误的,会导致编译错误。
  11. printf("%d\n", strlen(p[0]));:这和第10行的解释相同。p[0]是一个字符,不是一个指针。所以这行代码是错误的,会导致编译错误。
  12. printf("%d\n", strlen(&p));:这里也有一个问题。strlen函数的参数应该是一个指向字符的指针,但&p是一个指向指针的指针,不是一个指向字符的指针。所以这行代码是错误的,会导致编译错误。
  13. printf("%d\n", strlen(&p + 1));:这和第12行的解释相同。&p + 1是一个指针,不是一个指向字符的指针。所以这行代码是错误的,会导致编译错误。
  14. printf("%d\n", strlen(&p[0] + 1));:这和第12行的解释相同。&p[0] + 1是一个指针,不是一个指向字符的指针。所以这行代码是错误的,会导致编译错误。

二维整型数组字节数

//二维数组
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));  // 48
	printf("%d\n", sizeof(a[0][0]));  // 4
	printf("%d\n", sizeof(a[0]));  // 16
	printf("%d\n", a[0] + 1);  //是地址
	printf("%d\n", *(a[0] + 1));  //是元素1
	printf("%d\n", sizeof(a[0] + 1));  // 4
	printf("%d\n", sizeof(*(a[0] + 1)));  // 4
	printf("%d\n", *(a + 1));  //随机数
	printf("%d\n", sizeof(a + 1));  // 4
	printf("%d\n", sizeof(*(a + 1))); // 16
	printf("%d\n", sizeof(&a[0] + 1));  // 4
	printf("%d\n", sizeof(*(&a[0] + 1)));  // 16
	printf("%d\n", sizeof(*a));  //16
	printf("%d\n", sizeof(a[3])); // 16

这是每行代码的解释:

  1. printf("%d\n", sizeof(a)); // 48sizeof(a)返回数组a的总字节大小。因为a是一个包含3个元素的数组,每个元素又是一个包含4个int类型元素的数组,每个int类型元素在大多数系统上占4字节,所以sizeof(a)返回48。
  2. printf("%d\n", sizeof(a[0][0])); // 4a[0][0]是数组的第一个元素,是一个int类型的元素,所以sizeof(a[0][0])返回的是int的大小,即4。
  3. printf("%d\n", sizeof(a[0])); // 16a[0]是数组的第一个元素,是一个包含4个int类型元素的数组,所以sizeof(a[0])返回的是这个数组的总字节大小,即16。
  4. printf("%d\n", a[0] + 1); //是地址a[0] + 1是一个指向数组第二个元素的指针,所以这行代码打印的是这个指针的值,即数组第二个元素的地址。
  5. printf("%d\n", *(a[0] + 1)); //是元素1*(a[0] + 1)解引用了指向数组第二个元素的指针,所以这行代码打印的是数组的第二个元素的值,即1。
  6. printf("%d\n", sizeof(a[0] + 1)); // 4a[0] + 1是一个指针,所以sizeof(a[0] + 1)返回的是指针的大小。
  7. printf("%d\n", sizeof(*(a[0] + 1))); // 4*(a[0] + 1)是数组的第二个元素,是一个int类型的元素,所以sizeof(*(a[0] + 1))返回的是int的大小,即4。
  8. printf("%d\n", *(a + 1)); //随机数*(a + 1)解引用了指向数组第二个元素的指针,所以这行代码打印的是数组的第二个元素的值。但是,这个元素是一个包含4个int类型元素的数组,不能直接被转换为int,所以这行代码的行为是未定义的,可能会打印一个随机数。
  9. printf("%d\n", sizeof(a + 1)); // 4a + 1是一个指针,所以sizeof(a + 1)返回的是指针的大小。
  10. printf("%d\n", sizeof(*(a + 1))); // 16*(a + 1)解引用了指向数组第二个元素的指针,所以它的值是数组的第二个元素,即一个包含4个int类型元素的数组。所以sizeof(*(a + 1))返回的是这个数组的总字节大小,即16。
  11. printf("%d\n", sizeof(&a[0] + 1)); // 4&a[0] + 1是一个指针,所以sizeof(&a[0] + 1)返回的是指针的大小。
  12. printf("%d\n", sizeof(*(&a[0] + 1))); // 16*(&a[0] + 1)解引用了指向数组第二个元素的指针,所以它的值是数组的第二个元素,即一个包含4个int类型元素的数组。所以sizeof(*(&a[0] + 1))返回的是这个数组的总字节大小,即16。
  13. printf("%d\n", sizeof(*a)); //16*a解引用了指向数组第一个元素的指针,所以它的值是数组的第一个元素,即一个包含4个int类型元素的数组。所以sizeof(*a)返回的是这个数组的总字节大小,即16。
  14. printf("%d\n", sizeof(a[3])); // 16a[3]试图访问数组的第四个元素,但是数组只有3个元素,所以这行代码的行为是未定义的。然而,由于sizeof运算符不会实际执行它的操作数,所以这行代码不会导致运行时错误,而是返回的是一个包含4个int类型元素的数组的大小,即16。

希望这个解释能帮到你!

指针试题

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	printf("%p\n", &a[0]);  // 007EF7F8
	printf("%p\n", &a);     // 007EF7F8
	printf("%p\n", &a + 1);  // 007EF80C
	int *ptr = (int *)(&a + 1);  // 数组最后一个元素地址在加1,即加上4个字节
	printf("%d,%d", *(a + 1), *(ptr - 1));  // 第一个是数组第2个元素 2  第二个是数组最后一个数 5
	return 0;
}
//程序的结果是什么?

这是每行代码的解释:

  1. printf("%p\n", &a[0]); // 007EF7F8:这行代码打印的是数组第一个元素的地址。
  2. printf("%p\n", &a); // 007EF7F8:这行代码打印的是数组的地址。因为数组在内存中是连续存储的,所以数组的地址就是它的第一个元素的地址。
  3. printf("%p\n", &a + 1); // 007EF80C:这行代码打印的是数组的地址加上数组的大小。因为a是一个包含5个int类型元素的数组,每个int类型元素在大多数系统上占4字节,所以&a + 1的值是数组的地址加上20字节。
  4. int *ptr = (int *)(&a + 1); // 数组最后一个元素地址在加1,即加上4个字节:这行代码定义了一个指针ptr,并将它初始化为数组的地址加上数组的大小。所以ptr的值是数组最后一个元素的下一个位置的地址。
  5. printf("%d,%d", *(a + 1), *(ptr - 1)); // 第一个是数组第2个元素 2 第二个是数组最后一个数 5:这行代码打印的是数组的第二个元素的值和数组的最后一个元素的值。*(a + 1)解引用了指向数组第二个元素的指针,所以它的值是数组的第二个元素,即2。*(ptr - 1)解引用了指向数组最后一个元素的指针,所以它的值是数组的最后一个元素,即5。

所以,程序的输出结果是:

007EF7F8
007EF7F8
007EF80C
2,5

希望这个解释能帮到你!



//结构体的大小是20个字节
struct Test
{
	int Num;
	char *pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	printf("%p\n", p);  // 00000000(16进制)
	printf("%p\n", p + 0x1);//00000014(16进制)
	printf("%d\n", sizeof(unsigned long));  // 4
	printf("%d\n", sizeof(unsigned int));  // 4
	printf("%p\n", (unsigned long)p + 0x1);  // 00000001
	printf("%p\n", (unsigned int*)p + 0x1);  // 00000004
	return 0;
}

这是每行代码的解释:

  1. printf("%p\n", p); // 00000000(16进制):这行代码打印的是指针p的值。因为p的值是0x100000,所以这行代码打印的是0x100000
  2. printf("%p\n", p + 0x1);//00000014(16进制):这行代码打印的是指针p加上结构体Test的大小的值。因为p的值是0x100000,结构体Test的大小是20个字节,所以p + 0x1的值是0x100000 + 20 = 0x100014,所以这行代码打印的是0x100014
  3. printf("%d\n", sizeof(unsigned long)); // 4:这行代码打印的是unsigned long类型的大小。在大多数系统上,unsigned long类型的大小是4字节,所以这行代码打印的是4。
  4. printf("%d\n", sizeof(unsigned int)); // 4:这行代码打印的是unsigned int类型的大小。在大多数系统上,unsigned int类型的大小是4字节,所以这行代码打印的是4。
  5. printf("%p\n", (unsigned long)p + 0x1); // 00000001:这行代码打印的是将指针p转换为unsigned long类型后加上1的值。因为p的值是0x100000,所以(unsigned long)p + 0x1的值是0x100000 + 1 = 0x100001,所以这行代码打印的是0x100001
  6. printf("%p\n", (unsigned int*)p + 0x1); // 00000004:这行代码打印的是将指针p转换为unsigned int*类型后加上1的值。因为p的值是0x100000unsigned int类型的大小是4字节,所以(unsigned int*)p + 0x1的值是0x100000 + 4 = 0x100004,所以这行代码打印的是0x100004

所以,程序的输出结果是:

00000000
00000014
4
4
00000001
00000004

希望这个解释能帮到你!




int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int *ptr1 = (int *)(&a + 1);
	int *ptr2 = (int *)((int)a + 1);  // 4, 2000000
	printf("%x, %x", ptr1[-1], *ptr2);
	return 0;
}

这是每行代码的解释:

  1. int a[4] = { 1, 2, 3, 4 };:这行代码定义了一个包含4个int类型元素的数组a,并将它初始化为{ 1, 2, 3, 4 }
  2. int *ptr1 = (int *)(&a + 1);:这行代码定义了一个指针ptr1,并将它初始化为数组a的地址加上数组的大小。因为a是一个包含4个int类型元素的数组,每个int类型元素在大多数系统上占4字节,所以&a + 1的值是数组的地址加上16字节。所以ptr1的值是数组最后一个元素的下一个位置的地址。
  3. int *ptr2 = (int *)((int)a + 1);:这行代码定义了一个指针ptr2,并将它初始化为数组a的地址加上1。因为a的地址是一个指针,所以(int)a的值是这个指针的值,即数组的地址。所以ptr2的值是数组的地址加上1字节。
  4. printf("%x, %x", ptr1[-1], *ptr2);:这行代码打印的是数组的最后一个元素的值和数组的第一个元素的下一个字节的值。ptr1[-1]解引用了指向数组最后一个元素的指针,所以它的值是数组的最后一个元素,即4。*ptr2解引用了指向数组第一个元素的下一个字节的指针,所以它的值是这个字节的值。这个值是不确定的,因为它取决于在内存中紧接着数组的位置是否恰好有一个字节。

所以,程序的输出结果是:

4, 2000000

希望这个解释能帮到你!


#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int *p;
	p = a[0];
	printf("%d", p[0]);  // 1
	return 0;
}

这是每行代码的解释:

  1. int a[3][2] = { (0, 1), (2, 3), (4, 5) };:这行代码定义了一个包含3个元素的数组a,每个元素又是一个包含2个int类型元素的数组。然后,它试图将这个数组初始化为{ (0, 1), (2, 3), (4, 5) }。然而,这里的括号并不表示数组元素,而是表示逗号运算符。逗号运算符,会计算它的两个操作数,然后返回第二个操作数的值。所以(0, 1)的值是1,(2, 3)的值是3,(4, 5)的值是5。因此,这个数组实际上被初始化为{ 1, 3, 5 },剩下的元素被初始化为0。
  2. int *p;:这行代码定义了一个指针p
  3. p = a[0];:这行代码将指针p初始化为数组a的第一个元素的地址。因为a的第一个元素是一个包含2个int类型元素的数组,所以p的值是这个数组的地址。
  4. printf("%d", p[0]); // 1:这行代码打印的是指针p指向的元素的值。因为p的值是数组a的第一个元素的地址,所以p[0]的值是数组a的第一个元素的第一个元素,即1。

所以,程序的输出结果是:

1

希望这个解释能帮到你!


int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p, %d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);  // FFFFFFFC, -4
	return 0;
}

这段代码中,p是一个指向包含4个int类型元素的数组的指针,而a是一个包含5个包含5个int类型元素的数组的数组。所以,pa的类型是不同的。然而,这段代码试图将p初始化为a的值,这是不正确的,因为它们的类型是不同的。所以,这段代码的行为是未定义的,它可能会导致编译错误,或者在运行时产生不可预知的结果。

然后,这段代码试图打印两个指针之间的差值。这两个指针分别是&p[4][2]&a[4][2]。然而,由于pa的类型是不同的,所以这两个指针实际上并不指向同一类型的对象。因此,这两个指针之间的差值是未定义的。

所以,这段代码的输出结果是不确定的,它取决于具体的编译器和运行时环境。在某些情况下,它可能会打印FFFFFFFF, -4,但在其他情况下,它可能会打印其他的值,或者导致运行时错误。希望这个解释能帮到你!


int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int *ptr1 = (int *)(&aa + 1);
	int *ptr2 = (int *)(*(aa + 1));
	printf("%d\n", *aa);  // 19922324
	printf("%d\n", *(aa + 1));  // 19922344
	printf("%d, %d", *(ptr1 - 1), *(ptr2 - 1));  // 10 5

	return 0;
}

这段代码中,ptr1被初始化为数组aa的地址加上数组的大小,所以ptr1的值是数组最后一个元素的下一个位置的地址。ptr2被初始化为数组aa的第二个元素的值,即一个包含5个int类型元素的数组。所以,*(ptr1 - 1)的值是数组的最后一个元素,即10,*(ptr2 - 1)的值是数组的第五个元素,即5。

*aa*(aa + 1)的值取决于具体的编译器和运行时环境。在某些情况下,它们可能会打印出内存地址,但在其他情况下,它们可能会打印出其他的值。

所以,程序的输出结果可能是:

19922324
19922344
10, 5

但请注意,这个结果可能会因为具体的编译器和运行时环境而有所不同。希望这个解释能帮到你!


#include <stdio.h>
int main()
{
	char *a[] = { "work","at","alibaba" };
	char**pa = a;
	pa++;
	printf("%s\n", *pa);  // at

	return 0;
}

这段代码中,pa被初始化为数组a的地址,然后pa的值增加了1,所以pa的值是数组的第二个元素的地址。所以,*pa的值是数组的第二个元素,即字符串"at"。

所以,程序的输出结果是:

at

希望这个解释能帮到你!


int main()
{
	char *c[] = { "ENTER","NEW","POINT","FIRST" };//一级指针数组
	char**cp[] = { c + 3,c + 2,c + 1,c };  // 二级指针数组
	char***cpp = cp;  // 三级指针变量
	//printf("%d\n", *c[1]);  // ENTER的N
	//printf("%s\n", *(c + 1));  // NEW
	//printf("%s\n", *c+1);  // NTER
	printf("%s\n", **++cpp);  // POINT  cpp=&(c+3)  ++cpp=&(c+2) *++cpp=c+2的字符串地址  **++cpp=c+2的值
	printf("%s\n", *--*++cpp + 3);  // ER  cpp = &(c+2)  ++cpp=&(c+1) *++cpp=(c+1)的字符串地址  --*++cpp=(c+1)=c的字符串地址  *--*++cpp=c的值  *--*++cpp+3="ENTER"第三个下标开始
	printf("%p\n", *cpp[-2] + 3);  // ST  ???
	printf("%s\n", cpp[-1][-1] + 1);  // EW  ???

	return 0;
}

这段代码的运行结果如下:

POINT
ER
ST
EW

解释如下:

  1. printf("%s\n", **++cpp);cpp 是指向 cp 的指针,++cpp 使得 cpp 指向 cp 的下一个元素,即 c+2*++cpp 得到 c+2**++cpp 得到 c+2 所指向的字符串,即 “POINT”。
  2. printf("%s\n", *--*++cpp + 3);++cpp 使得 cpp 指向 cp 的下一个元素,即 c+1*++cpp 得到 c+1--*++cpp 得到 c+1 的前一个元素,即 c*--*++cpp 得到 c 所指向的字符串,即 “ENTER”。*--*++cpp+3 得到 “ENTER” 从第三个字符开始的子字符串,即 “ER”。
  3. printf("%p\n", *cpp[-2] + 3);cpp[-2] 得到 cp 的前两个元素,即 c+3*cpp[-2] 得到 c+3 所指向的字符串,即 “FIRST”。*cpp[-2]+3 得到 “FIRST” 从第三个字符开始的子字符串,即 “ST”。
  4. printf("%s\n", cpp[-1][-1] + 1);cpp[-1] 得到 cp 的前一个元素,即 c+2cpp[-1][-1] 得到 c+2 的前一个元素,即 c+1cpp[-1][-1]+1 得到 c+1 所指向的字符串,即 “NEW”,从第一个字符开始的子字符串,即 “EW”。

以上就是这段代码的运行结果及其解释。希望对你有所帮助!

编译器和运行时环境而有所不同。希望这个解释能帮到你!


#include <stdio.h>
int main()
{
	char *a[] = { "work","at","alibaba" };
	char**pa = a;
	pa++;
	printf("%s\n", *pa);  // at

	return 0;
}

这段代码中,pa被初始化为数组a的地址,然后pa的值增加了1,所以pa的值是数组的第二个元素的地址。所以,*pa的值是数组的第二个元素,即字符串"at"。

所以,程序的输出结果是:

at

希望这个解释能帮到你!


int main()
{
	char *c[] = { "ENTER","NEW","POINT","FIRST" };//一级指针数组
	char**cp[] = { c + 3,c + 2,c + 1,c };  // 二级指针数组
	char***cpp = cp;  // 三级指针变量
	//printf("%d\n", *c[1]);  // ENTER的N
	//printf("%s\n", *(c + 1));  // NEW
	//printf("%s\n", *c+1);  // NTER
	printf("%s\n", **++cpp);  // POINT  cpp=&(c+3)  ++cpp=&(c+2) *++cpp=c+2的字符串地址  **++cpp=c+2的值
	printf("%s\n", *--*++cpp + 3);  // ER  cpp = &(c+2)  ++cpp=&(c+1) *++cpp=(c+1)的字符串地址  --*++cpp=(c+1)=c的字符串地址  *--*++cpp=c的值  *--*++cpp+3="ENTER"第三个下标开始
	printf("%p\n", *cpp[-2] + 3);  // ST  ???
	printf("%s\n", cpp[-1][-1] + 1);  // EW  ???

	return 0;
}

这段代码的运行结果如下:

POINT
ER
ST
EW

解释如下:

  1. printf("%s\n", **++cpp);cpp 是指向 cp 的指针,++cpp 使得 cpp 指向 cp 的下一个元素,即 c+2*++cpp 得到 c+2**++cpp 得到 c+2 所指向的字符串,即 “POINT”。
  2. printf("%s\n", *--*++cpp + 3);++cpp 使得 cpp 指向 cp 的下一个元素,即 c+1*++cpp 得到 c+1--*++cpp 得到 c+1 的前一个元素,即 c*--*++cpp 得到 c 所指向的字符串,即 “ENTER”。*--*++cpp+3 得到 “ENTER” 从第三个字符开始的子字符串,即 “ER”。
  3. printf("%p\n", *cpp[-2] + 3);cpp[-2] 得到 cp 的前两个元素,即 c+3*cpp[-2] 得到 c+3 所指向的字符串,即 “FIRST”。*cpp[-2]+3 得到 “FIRST” 从第三个字符开始的子字符串,即 “ST”。
  4. printf("%s\n", cpp[-1][-1] + 1);cpp[-1] 得到 cp 的前一个元素,即 c+2cpp[-1][-1] 得到 c+2 的前一个元素,即 c+1cpp[-1][-1]+1 得到 c+1 所指向的字符串,即 “NEW”,从第一个字符开始的子字符串,即 “EW”。

以上就是这段代码的运行结果及其解释。希望对你有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值