C语言——指针

目录

前言

指针初识

1指针是什么

2指针变量

3指针类型

3.1指针+-整数

3.2指针的解引用

4野指针

4.1野指针成因

a指针未初始化

b指针越界访问

c指针指向的空间被释放了

4.2避免野指针

5指针运算

5.1指针+-整数

5.2指针的关系运算

5.3指针-指针

6指针和数组

7二级指针

8指针数组

指针进阶

1字符指针

2指针数组

2.1数组名VS&数组名

2.2数组指针用途

3数组传参

3.1一维数组的传参

3.2二维数组的传参

4函数指针

5函数指针数组

6回调函数

指针和数组笔试题

指针笔试题

题1

题2

题3

​编辑题4

题5

题6

题7

题8


前言

学习指针相关的知识可能是你学习C语言碰到的第一道'难关',但它非常重要,重要到后面无时无刻都会出现它来引出新知识点或者通过它来理解新知识,所以必须要好好地掌握好指针!

指针初识

1指针是什么

理解指针的2个要点:

  1. 指针是内存中一个最小单元的编号,也就是地址;

  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

指针就是地址(单元编号),口语(语法)上指针就是指针变量

2指针变量

通过&(取地址操作符)取出变量的内存起始地址,将其存放到一个变量中

cpp 复制代码
#include <stdio.h>
int main()
{
    int a = 10;//在栈上申请4个字节的空间
    int *p = &a;//这里我们对变量a,进行取出它的地址
    //这里是将a的4个字节的第一个字节的地址存放在p变量中!
    return 0;
}

那这里就会有问题了:为什么内存要划分为一个字节对应一个地址,两个字节对应一个地址不行吗

这时因为经过前人仔细的计算和权衡发现:一个字节给一个对应的地址是比较合适的~

那在计算机的世界里,它是如何进行编址的呢?

对于32位的机器,在机器里面会有32根地址线,那么假设每根地址线在寻址的时候会产生高电平(高电压)和低电平(低电压)也就是就是(1或者0):

那么就会有2^32的地址:相当于4GB大小的内存空间来给我们进行编址;

这也就是说:

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节;同理64位的机器上指针变量就是8个字节!

3指针类型

我们都知道:不同的变量会有不同的类型,如:int,char,double,float..那么指针会有类型吗?答案是有的:

cpp 复制代码
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;

指针的类型由变量类型(type)+*组成:那既然指针类型全都是4字节(32位下),把指针类型规定成ptr*一种指针类型就行了,干嘛还要有这么多指针类型的组合?

3.1指针+-整数

cpp 复制代码
#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	//选择不同指针类型会有什么变化
	printf("&n = %p\n", &n);
	printf("pc = %p\n", pc);
	printf("pi = %p\n", pi);
	//加1又会有什么变化
	printf("pc+1 = %p\n", pc + 1);
	printf("pi+1 = %p\n", pi + 1);
	return 0;
}

结果:

我们发现:使用不同指针类型来储存地址没有任何变化;而pc(char*)加1是跳过的是1个字节,pi(int*)加1是跳过的是4个字节;

作用1:不同指针类型决定了它先前(先后)走一步有多大

3.2指针的解引用

cpp 复制代码
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	int* pi = &n;
	//分别进行两次调试来观察内存中n的变化
	*pc = 0;
	//*pi = 0;
	return 0;
}

pc的变化过程

pi的变化过程

pc只修改了1个字节,而pi修改了4个字节;这也就是说:

作用2:指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)

而在我们日常写代码中,如果想要把数组(int类型)里的数据全部修改成0,就只能用int*的指针类型(只有它能符合条件,其它像char*在同样的循环下它只能修改数组数据的1/4)

4野指针

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

4.1野指针成因

a指针未初始化
cpp 复制代码
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
b指针越界访问
cpp 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}
c指针指向的空间被释放了
cpp 复制代码
#include <stdio.h>

int* test()
{
	int a = 10;//0x12ff40
	return &a;
}
int main()
{
	int* p = test();
	//a变量所在的函数被释放了
	printf("%p\n", *p);
	return 0;
}

这也就相当于:你女朋友跟你分手了,你还拿着她的电话号码给他打电话一样!属于'非法骚扰'!

4.2避免野指针

  1. 定义指针时,如果不知道要作什么用途时必须进行初始化(NULL)
  2. 小心指针越界(越界访问)
  3. 指针指向空间释放时要及时置NULL
  4. 避免返回局部变量的地址(常见的是返回用户定义的函数中的临时变量的地址进行返回)
  5. 指针使用之前检查有效性(使用指针是要先判断)
cpp 复制代码
#include <stdio.h>
int main()
{
    int *p = NULL;
    //....
    int a = 10;
    p = &a;
    if(p != NULL)
    {
        *p = 20;    
    }
    return 0;
}

5指针运算

5.1指针+-整数

cpp 复制代码
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	for (int i = 0; i < 10; i++)
	{
		//p+i指跳过多少个元素(i+sizeof(int)个字节)
		//printf("%d ", *(p + i));
		printf("%d ", i[arr]);
		//在这里:p == arr
		//        p+i == arr+i
		//        *(p+i) == *(arr+i) == arr[i]
		//交换律  *(i+p) == *(i+arr) == i[arr]!
	}
	return 0;
}

结果:

5.2指针的关系运算

cpp 复制代码
#inlcude<stdio.h>

#define N_VALUES 5
float values[N_VALUES];
float *vp;

for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。(前面可能存在重要数据)

5.3指针-指针

指针-指针前提:两个指针必须指向的是同一块内存区域

cpp 复制代码
#include <stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	//得到的是指针-指针的绝对值=指针与指针之间的元素个数
	printf("%d\n", &(arr[4]) - &(arr[0]));
	return 0;
}
//可用于模拟实现strlen
int my_strlen(char *s)
{
    char *p = s;
    while(*p != '\0' )
    p++;
    return p-s;
}

6指针和数组

先来看一个例子:

cpp 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	printf("arr     = %p\n", arr);
	printf("&arr[0] = %p\n", &arr[0]);
	return 0;
}

结果:

在这说明:数组名就是数组首元素地址;

但数组名有两个例外:

a.sizeof(arr)计算:这里计算的是整个数组的大小!

b.&数组名:虽然打印出来还是数组首元素地址,但它表示的是数组整个数组的地址!

cpp 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	printf("sizeof(arr) = %d\n", sizeof(arr));
	printf("arr         = %p\n", &arr);
	printf("arr+1       = %p\n", &arr+1);
	printf("&arr[0]     = %p\n", &arr[0]);
	printf("&arr[0]+1   = %p\n", &arr[0]+1);
	return 0;
}

结果:

7二级指针

指针类型也是变量,是变量也是地址,那么用什么类型用储存它的地址呢?

答案是:用二级指针变量

8指针数组

指针数组是指针还是数组?

答案:是数组。是存放指针的数组

与前面整形指针与字符数组类似:但指针数组里存放的是int*类型的元素

cpp 复制代码
#include<stdio.h>
//利用指针数组模拟二维数组
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j=0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

指针进阶

1字符指针

在指针类型中有一种字符指针:char*

cpp 复制代码
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

另一种用法:

cpp 复制代码
#include<stdio.h>
int main()
{
	//这里是把一个字符串放到pstr指针变量里了吗?
	// pstr储存只是常量字符串的首元素地址!
	const char* pstr = "hello bit.";
	printf("%s\n", pstr);
	return 0;
}

注意:常量字符串是不能被修改的,一般char*要表达常量字符串时要加const!

一道面试题:

cpp 复制代码
#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const 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;
}

结果:

解释:

2指针数组

数组指针是指针?还是数组?

答案是:指针

我们已经熟悉:

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

那数组指针应该是:能够指向数组的指针

2.1数组名VS&数组名

前面我们讲了:数组名就是首元素地址,但有两个例外:

sizeof(数组名)和&数组名;其中&数组名表示的是整个数组的地址!

那我们怎么把它的地址给指针呢?

cpp 复制代码
#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    //理解:*表面p你是一个指针,加上括号表示*要先跟p结合,不然就成了指针数组
    int (*p)[10] = &arr;
    return 0;
}

有的人可能有这种想法:int [10] *p int [10]表面类型是int数组,*标明p是指针

虽然有道理,但语法可不是怎么规定的~

那指针数组到底有什么用呢?

2.2数组指针用途

主要用于二维数组的传参!

cpp 复制代码
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	for (int 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)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[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_arr1(arr, 3, 5);
	printf("\n");
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

打印出来的效果是一样的:

学习了指针数组,来看看下面的代码各是什么意思?

cpp 复制代码
int arr[5];            //arr是数组,是存放5个int类型数据的数组
int *parr1[10];        //parr1是数组,是存放10个int*类型数据的指针数组
int (*parr2)[10];      //parr2是指针,是存放10个int类型数据的数组的指针数组
int (*parr3[10])[5];   //parr3是数组,是存放10个数组指针的数组(数组指针指向数组里5个int元素)

3数组传参

3.1一维数组的传参

cpp 复制代码
#include <stdio.h>
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? 行,arr2数组里变量类型是int* 用int**接收
{}
int main()
{
	int arr[10] = { 0 }; 
    int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

3.2二维数组的传参

cpp 复制代码
void test(int arr[3][5])//ok?行
{}
void test(int arr[][])//ok? 不行,行能省,列不行
{}
void test(int arr[][5])//ok?行,同上
{}
void test(int* arr)//ok?不行,arr是二维数组,传过来的是第一行的地址,不能用int类型接收
{}
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);
}

4函数指针

cpp 复制代码
#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}

输出的都是地址,谁的地址?函数地址;而加不加&都是指的是函数地址,无函数首元素地址之分

是地址我们就能用指针来储存,用什么指针?函数指针!

cpp 复制代码
#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	void (*p)() = test;
	//调用
	p();
	//加上*也行,但没必要~
	(*p)();
	(**p)();
	return 0;
}

下面我们来阅读两个'有趣'的代码:

cpp 复制代码
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

代码1:这是调用0地址处的函数,该函数返回类型是void,参数为空

代码2:这是一次函数声明:声明signal函数;该函数参数类型:

第一个是int类型;第二个是void(*)(int)函数指针类型:该函数参数是int类型,返回值为空

返回值是一个void(*)(int) 函数指针类型:该函数参数是int类型,返回值为空

我们可以将代码2进行修改

cpp 复制代码
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
//void(*)(int) signal(int,void(*)(int));//看着好像可以,但语法不支持!

5函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,那函数的地址能不用用数组来存呢?答案是可以的~

cpp 复制代码
int (*parr1[10])();// 存着10个函数指针:参数为空,返回类型是int

那具体有什么用呢?请看下面的例子:实现一个计算器

通常我们可能会这样写:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#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;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

这样写虽然能实现我们要的结果,但代码整体看上去不优雅:如果计算器在多几个的话,case语句要写很多行!

我们选用函数指针数组的方式来写:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#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);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}

6回调函数

回调函数是通过函数指针来调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数(此时它就是回调函数)。

回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应!!这点在后面设计代码时非常重要!!!

例子:qsort函数(用来排序任意类型数据)

qsort的各个参数及返回值

cpp 复制代码
void qsort (void* base,    //待排序数组的第一个元素地址
            size_t num,    //数组个数
            size_t size,   //数组元素的类型
            int (*compar)(const void*,const void*));  //传自己设计出来的比较函数的地址

在这里:void* :无具体类型的指针,用来存放任意类型数据的地址,但不能进行解引用操作,也不能进行+-操作

简单进行使用

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
//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;
}

排结构体类型:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

struct Stu
{
	char name[20];
	int age;
};

int struct_cmp(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int main()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",18},{"wangwu",25} };
	int i = 0;
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), struct_cmp);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%s %d ", arr[i].name,arr[i].age);
	}
	printf("\n");
	return 0;
}

改造qsort函数(利用冒泡排序来实现)来见见回调函数的使用场景

改造之前我们要解决:

1自己写的cmp函数解决的是什么问题 -> 要怎么排序

2使用传进来的cmp函数参数要怎么传 -> 使用最小的类型char来+-总字节准确定义地址

3交换两个数据位置直接交换 -> 一个字节一个字节交换

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

void Swap(char* p1, char* p2,int size)
{
    //一个字节一个字节交换
	for (int i = 0; i < size; i++)
	{
		char tmp=*(p1+i);
		*(p1+i) = *(p2+i);
		*(p2+i) = tmp;
	}
}

void Myqsort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			//if(base[j]>base[j+1])
			if (cmp((char*)base+j*size,(char*)base+(j+1)*size) > 0)//排升序
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
			}
		}
	}
}

struct Stu
{
	char name[20];
	int age;
};

int int_cmp(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
	//return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age;//排降序只需改变两个数据位置

}
int main()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",18},{"wangwu",25} };
	int i = 0;
	//qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), int_cmp);
	Myqsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), int_cmp);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%s %d ", arr[i].name,arr[i].age);
	}
	printf("\n");
	return 0;
}

结果:

指针和数组笔试题

cpp 复制代码
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));        // 16 a:整个数组
printf("%d\n",sizeof(a+0));      // 4/8 a+0:首元素地址
printf("%d\n",sizeof(*a));       // 4 *a == a[0]
printf("%d\n",sizeof(a+1));      // 4/8 a+1 == &a[1]
printf("%d\n",sizeof(a[1]));     // 4 a[1]:下标为1的元素
printf("%d\n",sizeof(&a));       // 4/8 &a:取出整个数组的地址(类型:int (*)[4])
printf("%d\n",sizeof(*&a));      // 16 *&抵消
printf("%d\n",sizeof(&a+1));     // 4/8 &a+1:跳过整个数组的地址
printf("%d\n",sizeof(&a[0]));    // 4/8 &a[0]:首元素的地址
printf("%d\n",sizeof(&a[0]+1));  // 4/8 &a[0]+1:第二个元素的地址
cpp 复制代码
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));           //6
printf("%d\n", sizeof(arr+0));         //4/8
printf("%d\n", sizeof(*arr));          //1
printf("%d\n", sizeof(arr[1]));        //1
printf("%d\n", sizeof(&arr));          //4/8
printf("%d\n", sizeof(&arr+1));        //4/8
printf("%d\n", sizeof(&arr[0]+1));     //4/8

printf("%d\n", strlen(arr));           //随机值 arr是首元素地址,往后遇到'\0'才停止
printf("%d\n", strlen(arr+0));         //随机值 arr+0也是首元素地址
printf("%d\n", strlen(*arr));          //*arr是'a';strlen会认为你传进来的是一个97(a)的地址,进行解引用会造成非法访问,所以会err
printf("%d\n", strlen(arr[1]));        //err 原理同上(只不过此时取的是87(b)的地址)
printf("%d\n", strlen(&arr));          //随机值 但此时的类型是:int (*)[6],会发生类型转化
printf("%d\n", strlen(&arr+1));        //随机值 原理同上
printf("%d\n", strlen(&arr[0]+1));     //随机值 原理同上

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));           //7 包含'\0'
printf("%d\n", sizeof(arr+0));         //4/8 首元素地址
printf("%d\n", sizeof(*arr));          //1 取出'a'
printf("%d\n", sizeof(arr[1]));        //1 取出'b'
printf("%d\n", sizeof(&arr));          //4/8 数组的地址
printf("%d\n", sizeof(&arr+1));        //4/8
printf("%d\n", sizeof(&arr[0]+1));     //4/8

printf("%d\n", strlen(arr));           //6 到'\0'停止
printf("%d\n", strlen(arr+0));         //6
printf("%d\n", strlen(*arr));          //err
printf("%d\n", strlen(arr[1]));        //err
printf("%d\n", strlen(&arr));          //6 但会发生类型转化
printf("%d\n", strlen(&arr+1));        //随机值 此时从'\0'之后开始找'\0'
printf("%d\n", strlen(&arr[0]+1));     //随机值

char *p = "abcdef";   // 类型为char*的p存在字符串的首元素地址
printf("%d\n", sizeof(p));             //4/8 p是指针变量
printf("%d\n", sizeof(p+1));           //4/8 
printf("%d\n", sizeof(*p));            //1 *p=='a'
printf("%d\n", sizeof(p[0]));          //1
printf("%d\n", sizeof(&p));            //4/8 但类型是char**
printf("%d\n", sizeof(&p+1));          //4/8 此时跳过的是4/8个字节的大小
printf("%d\n", sizeof(&p[0]+1));       //4/8

printf("%d\n", strlen(p));             //6
printf("%d\n", strlen(p+1));           //5
printf("%d\n", strlen(*p));            //err
printf("%d\n", strlen(p[0]));          //err
printf("%d\n", strlen(&p));            //随机值 取的是p的地址,不知道什么时候遇到'\0'
printf("%d\n", strlen(&p+1));          //随机值
printf("%d\n", strlen(&p[0]+1));       //5
cpp 复制代码
//二维数组
int a[3][4] = {0};                    
printf("%d\n",sizeof(a));            //48 a是数组名单独放在sizeof内部,计算的是整个数组的大小
printf("%d\n",sizeof(a[0][0]));      //4 a[0][0]:数组第一行第一个元素
printf("%d\n",sizeof(a[0]));         //16 a[0]:第一行第一个元素的地址,单独放在sizeof内部,计算的是第一行的大小  a[0] -- &a[0][0]
printf("%d\n",sizeof(a[0]+1));       //4/8 a[0]+1 第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));    //4 *(a[0]+1) -> *(&a[0][1]) 第一行第二个元素
printf("%d\n",sizeof(a+1));          //4/8 a并没有单独放在sizeof内部,也没有&,所以计算的是指针类型:int (*)[4]的大小
printf("%d\n",sizeof(*(a+1)));       //16 *(a+1) -> a[1] -> &a[1][0],计算的是第二行的大小
printf("%d\n",sizeof(&a[0]+1));      //4/8 &a[0]+1:第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));   //16 第二行的大小
printf("%d\n",sizeof(*a));           //16 第一行的大小
printf("%d\n",sizeof(a[3]));         //16 不会越界 值属性+类型属性 sizeof看的是类型属性

指针笔试题

题1

cpp 复制代码
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
//打印结果?
}

题2

cpp 复制代码
//已知,结构体Test类型的变量大小是20个字节
//X86环境下演示
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;

int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);//跳过20个字节
	printf("%p\n", (unsigned long)p + 0x1);//跳过1个字节:因为转成了无符号长整型,不是指针类型!
	printf("%p\n", (unsigned int*)p + 0x1);//跳过4个字节
    //打印结果
	return 0;
}

题3

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

题4

cpp 复制代码
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };//这时逗号表达式!!!
    int *p;
    p = a[0];
    printf( "%d", p[0]);//所以p[0] = 1
    return 0;
}

题5

cpp 复制代码
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]);//-4变成地址要转成补码存
    return 0;
}

题6

cpp 复制代码
int main()
{
 int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 int *ptr1 = (int *)(&aa + 1);// &aa+1:跳过这个二维数组
 int *ptr2 = (int *)(*(aa + 1));// *(aa+1) -> aa[1] -> &aa[1][0]
 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1)); //9 5
 return 0;
}

题7

cpp 复制代码
int main()
{
    char *a[] = {"work","at","alibaba"};
    char**pa = a;
    pa++;
    printf("%s\n", *pa);
    return 0;
}

题8

cpp 复制代码
int main()
{
    char *c[] = {"ENTER","NEW","POINT","FIRST"};
    char**cp[] = {c+3,c+2,c+1,c};
    char***cpp = cp;
    printf("%s\n", **++cpp); 
    printf("%s\n", *--*++cpp+3);
    printf("%s\n", *cpp[-2]+3);
    printf("%s\n", cpp[-1][-1]+1);
    return 0;
}

以上便是指针的全部内容,有错误欢迎在评论区指正,感谢观看!

相关推荐
在在进步17 分钟前
R学习——数据框
开发语言·r语言
大G哥22 分钟前
27. 聚类分析 - 使用R进行客户分群
开发语言·信息可视化·r语言
我不是程序猿儿42 分钟前
【C#】反射 和 特性(Attribute)、[AttributeUsage(AttributeTargets.Property)]
开发语言·c#
妈妈说名字太长显傻1 小时前
【C++】string类
开发语言·c++
丢丢丢丢丢丢~1 小时前
c++创建每日文件夹,放入每日日志
开发语言·c++
華華3551 小时前
读程序题...
开发语言·c++·算法
nwsuaf_huasir1 小时前
matlab一下特殊矩阵的操作(对角矩阵、全零行删除)
开发语言·matlab·矩阵
围观岳老师1 小时前
JAVA根据Word模板生成word文件
java·开发语言·word
是十一月末1 小时前
Linux的基本功能和命令
linux·服务器·开发语言·数据库