当前位置: 首页 > news >正文

C语言指针比较高级的用法及指针数组面试题详解

指针基础知识:https://blog.csdn.net/qq_47406941/article/details/109082884

一、字符指针

字符指针有以下两种使用方法:

1.字符指针“存储”单个字符

char c = 'a';

char*  pc = &c;

*pc = 'a';

2.字符指针“存储”字符串

char* str = "hello world";

printf("%s\n",str);//打印结果:hello world

我们知道,当指针“存储”单个字符时,实际上指针存储的是该字符的地址,当对该字符指针进行解引用操作时直接访问该地址即可。那么,指针“存储”字符串是怎么存储的呢?

首先,我们知道指针的大小只有四个字节的空间,而且指针存储的是变量的地址,因此指针不可能真正的“存储”一个字符串。那么指针到底是如何存储字符串的呢?

实际上,指针存储字符串时存储的是字符串首元素的地址。在访问该字符串时,先通过指针找到首元素的地址,在根据首元素的地址访问字符串中的元素,直到遇到结束标志符‘\0’停止访问。

判断下面输出语句的输出结果:

这里str3str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域, 当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1str2不同,str3str4不同。

二、数组指针

数组指针是指指向数组的指针。数组指针与其他类型指针一样,每+1加类型个字节(例如int型加一加四个字节,char型加一加一个字节,数组指针每加一加数组大小个字节)。

数组指针的定义:

char (*p)[10];

由于[]的优先级高于*的优先级,所以要给*p加上括弧变成(*p)表明p是一个指针类型。而后面的[10]说明的是该类指针是一个含有10个char型变量的数组指针。

 辨析数组的地址和数组首元素的地址:

有以下数组

int a[10];

a和&a各表示什么呢?哪一个是数组的地址,那一个是数组首元素的地址?

很明显,a是数组首元素的地址,&是数组的地址 。

从图中我们可以看出,a的值和&a的值相等,而a+1的值比&a+1的值小40(10个整型的大小,刚好是该数组a的大小)。所以,可以得出结论:数组的地址和数组首元素的地址的值是相等的,但是数组的地址+1加的是整个数组,首元素的地址+1加的是一个元素的大小。

三、指针数组

指针数组是指存储指针变量的数组,指针数组的定义如下:

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

四、数组传参和指针传参

一维数组传参

一位数组的传参有以下几种方式:

int main()

{

int arr[10]={0};

test(arr);

}

void test(int arr[]){}//数组传参用数组接受肯定是可以的

void test(int* arr){}//数组传参传的时首元素的地址,所以也可以用指针来接受

判断下列一位数组传参是否正确:

int main()

{

int*arr[10]={0};

test(arr);

}

void test(int*arr[]){}//正确

void test(int**arr){}//正确;指针数组存储的是地址,传地址的地址用二级指针来接受也是没有问题的。

二维数组传参

规定,二维数组传参时数组的行数可以省略而列数不可以省略。所以,有以下传参方式:

void test(int arr[3][5])

void test(int arr[][5])

void test(int arr[3][])//这种省略列数的是不可行的

那么,下面这写二位数组的传参方式是否正确呢?

 
void test ( int * arr ) //二维数组的数组名指向的数组的首元素的地址,而二维数组的首元素是一个一维数组,所以应该用数组指针来接收
{}
void test ( int* arr [ 5 ]) //这里的参数是一个指针数组,因此错误
{}
void test ( int ( * arr )[ 5 ]) //正确
{}
void test ( int ** arr ) //错误,二维数组的数组名是数组指针不是二级指针
{}
int main()
{
int arr[3][5];
test(arr);
}
 

一级指针传参

一级指针传参有下面两种方式:

int main()

{

int a = 1;

int*  b = 'c';

test(&a);//传变量的地址

test(b);//传指针类型的变量

}

void test(int*a){}

二级指针传参

二级指针传参有以下几种方式:

int main()

{

char c = 'b';

char*pc = &c;

char**ppc = &pc;

char* arr[10];

test(&pc);//传一级指针的地址

test(ppc);//传二级指针变量

test(arr);//传指针数组的数组名

return 0;

}

void test(char** p){}

无、函数指针

函数指针就是指指向函数的指针。例如:void  test(){}对函数名test取地址(&test)就是一个函数指针,那么函数指针如何定义呢?

函数指针的定义

void (*pfun)();//变量pfun就是一个函数指针

函数指针的使用---改进的冒泡排序

冒泡排序的算法我们都很熟悉,依次遍历一组数据,每一次遍历让最小(最大)的数遍历到数组的开头,经过n-1次的遍历将数组变成一个降序(升序)的数组。那么我们有时可能既要对一组数进行升序排序又要进行降序排序,这样的话我们就要写两个函数依次进行升序和降序,但是,冒泡排序升序和降序程序大多数代码都是相同的,如果写两个代码的复用率就会大大降低,此时我们就可以采用函数指针来实现这个要求。代码如下:

void PopSort(int* nums,int numsSize,int(*p)(int,int))
{
	int i = 0,j;
	int mid = 0;
	for (j = 0; j < numsSize - 1;j++)
	for (i = 0; i < numsSize - 1;i++)
	if (p(nums[i],nums[i+1]))
	{
		mid = nums[i];
		nums[i] = nums[i+1];
		nums[i + 1] = mid;
	}
}
int GreaterPopSort(int x,int y)
{
	return x > y ? 1 : 0;
}
int LessPopSort(int x, int y)
{
	return x < y ? 1 : 0;
}
int main()
{
	int i = 0;
	int nums[] = {1,5,6,3,2,8,9,4,7};
	//升序
	PopSort(nums, 9, &GreaterPopSort);
	for (i = 0; i<9; i++)
		printf("%2d", nums[i]);
	printf("\n");
	//降序
	PopSort(nums, 9, &LessPopSort);
	for (i = 0; i<9; i++)
		printf("%2d", nums[i]);
	printf("\n");
	return 0;
}

运行结果如下:

六、 函数指针数组

函数指针数组是存放函数指针的数组。定义如下:

int (*prt[10])();//表示数组ptr含有十个元素,每个元素都是一个int(*)()的指针

七、 指向函数指针数组的指针

指向函数指针数组的指针是一个指针,这个指针存储的是一个数组的地址,这个数组存的类型时函数指针类型。定义如下:

void (*(*ppfunArr)[10])(const char*)

八、回调函数

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

例如:上面我们讲过的函数指针解决冒泡排序就是一个回调函数的应用。当我们把GreaterPopSort或者LessPopSort的地址传递给PopSort函数时,PopSort在执行到相应地方时就会去调用这两个函数。

判断下列输出语句的输出结果:
part1:

part2:

part3:

part4:

:

part5

判断下列程序运行结果:

程序1:

int main ()
{
    int a [ 5 ] = { 1 , 2 , 3 , 4 , 5 };
    int * ptr = ( int * )( & a + 1 );
    printf ( "%d,%d" , * ( a + 1 ), * ( ptr - 1 ));
  //2,5;*(a+1)=a[1]=2,*(ptr-1)=a[4]=5;
    return 0 ;
}
// 程序的结果是什么?

程序2:

程序3

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 );//4,2000000
    return 0 ;
}

程序4

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

程序5

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

程序6

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,%d" , * ( ptr1 - 1 ), * ( ptr2 - 1 ));//10,5
    return 0 ;
}

程序7

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

程序8

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 ;
}
//结果
POINT
ER
ST
EW
 

相关文章:

  • C语言自定义类型详讲---结构体、联合、枚举
  • Git提交代码问题---push代码报错“fatal:HttpRequestException encountered”解决方案
  • C语言动态内存管理
  • C语言文件操作
  • C语言程序的编译和链接
  • nowcoder---统计子串个数(C语言)
  • LeetCode---搜索二维矩阵(c语言)
  • C语言编程过程中与到的问题(不定时更新)
  • 计算机中加减乘除的实现
  • Linux基本指令+使用举例、权限的认识和相关指令、Linux添加删除用户、shell的理解(超详细)
  • Linux连接网络、修改账户密码
  • LeetCode---删除排序数组中的重复项(C语言)
  • LeetCode---合并两个有序数组
  • leetcode---返回链表的中间节点
  • 学生成绩管理系统---数据结构、C语言课程设计
  • 《剑指offer》分解让复杂问题更简单
  • 2017-09-12 前端日报
  • gf框架之分页模块(五) - 自定义分页
  • js中forEach回调同异步问题
  • MYSQL 的 IF 函数
  • Python - 闭包Closure
  • Redis中的lru算法实现
  • spring boot下thymeleaf全局静态变量配置
  • 大快搜索数据爬虫技术实例安装教学篇
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 翻译:Hystrix - How To Use
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 你不可错过的前端面试题(一)
  • 前端技术周刊 2019-02-11 Serverless
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 写代码的正确姿势
  • AI算硅基生命吗,为什么?
  • kubernetes资源对象--ingress
  • postgresql行列转换函数
  • #微信小程序:微信小程序常见的配置传值
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (LeetCode C++)盛最多水的容器
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (独孤九剑)--文件系统
  • (四)Linux Shell编程——输入输出重定向
  • (译) 函数式 JS #1:简介
  • (转)linux 命令大全
  • .net core使用ef 6
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .NET 中 GetProcess 相关方法的性能
  • .NET处理HTTP请求
  • /bin、/sbin、/usr/bin、/usr/sbin
  • @javax.ws.rs Webservice注解
  • [ C++ ] STL---string类的模拟实现
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • [145] 二叉树的后序遍历 js
  • [ABC294Ex] K-Coloring