目录
1.字符指针
const char * p = "abcdef"//表示把字符串首字符的地址赋给了p,字符串实际内容为abcdef\0
//如果不加const可能会报警告,不安全,因为常量字符串无法修改
//但p的权限却变大了,会允许修改,但若修改,系统崩溃
经典代码:
#include <stdio.h>
int mian(){
char * p1 = "abcde";
char * p2 = "abcde";
char str1[] = "abcde";
char str2[] = "abcde";
if(p1 == p2)
printf("p1 == p2\n");
else
printf("p1 != p2\n");
if(str1 == str2)
printf("str1 == str2\n");
else
printf("str1 != str2\n");
return 0;
}
原因:1.常量字符串放在内存的只读数据区里面,因此相同的常量内存只会存一份;第二种创建数组需要自己的独立空间,两个数组的地址肯定不一样
2.指针数组
int * arr1[10];//整型指针的数组
char * arr[10];//一级字符指针的数组
char **arr3[5];//二级字符指针的数组
3.数组指针
3.1定义
指向数组的指针,用来存放数组的地址,与二级指针没一点关系
3.2&数组名VS数组名
&数组名得到的是数组的地址
数组名是数组首元素的地址
例如:
int arr[10];
int (*p)[10] = &arr; //定义数组指针时,数组元素个数不可省
3.3用途
一般可用于二维数组传参
例如:
void Print1(int (*p)[5],int r){
int i = 0;
int j = 0;
for(i = 0;i < r;i++){
for(j = 0;j < 5;j++){
printf("%d ",p[i][j]);l
}
printf("\n");
}
}
int main(){
int arr[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
Print1(arr,3);
return 0;
}
4.数组参数,指针参数
4.1一维数组传参
格式:
//1.1void test(int arr[]){}
//1.2void test(int arr[10]){}
//1.3void test(int * arr){}
//2.1void test2(int * arr[10]){}
//2.2void test2(int ** arr){} //因为此时首元素的地址就是一个二级地址,首元素是一级指针
int main(){
int arr[10] = {0};
int * arr2[10] = {0};
test(arr);
test(arr2);
retutn 0;
}
4.2二维数组传参
格式:
void test(int arr[3][5]){}
void test(int arr[][5]){}
void test(int (*arr)[5])
int main(){
int arr[3][5] = {0};
test(arr);
return 0;
}
5.函数指针
5.1定义
指向函数的指针(函数没有首地址和起始地址的概念,就是函数的地址)
5.2格式
函数返回类型 (*指针变量名) (函数形参类型) = &函数名称(&加与不加无影响)
int ADD(int a,int b){
return a+b;
}
int mian(){
//ADD自身就是地址,&ADD也是他的地址,两种表达方式没有区别
int (*p)(int,int) = &Add;//&加与不加都一样,形参变量名加与不加没影响
int (*p)(int,int) = ADD;//二者相同
int ret = p(3,5); //使用格式1
int ret = (*p)(3,5);//使用格式2
//这二者没有区别,*没有意义,只是表达的更明确,加几个都可以,但如果要加*一定要加括号
return 0;
}
//函数指针重命名举例
typedef int(*p_tf)(int,int); //即将int(*)(int,int)这种函数指针类型重命名为p_tf
5.3经典代码示例
5.3.1示例1
(* ( void(*)() ) 0)(); //一次函数调用
解释:
- 在括号内加个p,变成void(*p)(),此时里面的p是函数指针,因此void(*)()相当于函数指针类型,类似于整型指针类型int *和字符指针类型char *,在这里起强制类型转换的意思,将0转换成函数指针,指向地址为0的函数
- 接着调用地址为0的无参无返回值的函数
5.3.2示例2
void (* signal( int, void(*)(int) ) )(int); //三次函数调用
解释:
- void(*)(int)是无返回值函数指针类型,是作为signal函数指针的参数
- signal是第二层的函数指针,且返回值也为函数指针
- 最外层调用由signal函数指针返回的函数指针,且返回的函数指针只有一个整型参数
5.3.3示例3(用途举例)(实现计算器且避免数据冗余)
#include <stdio.h>
int Add(int x, int y) {
return x+y;
}
int Sub(int x, int y) {
return x - y;
}
int Mul(int x, int y) {
return x * y;
}
int Div(int x, int y) {
return x / y;
}
void Mule() {
printf("***********************\n");
printf("****1.加法 2.减法****\n");
printf("****3.乘法 4.除法****\n");
printf("**** 0.退出 *****\n");
}
void calc(int (*pd)(int, int)) {
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数->");
scanf("%d %d", &x, &y);
ret = pd(x, y);
printf("%d\n", ret);
}
int main() {
int input = 0;
do {
Mule();
scanf("%d", &input);
switch (input) {
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误\n");
}
} while (input);
return 0;
}
6.函数指针数组(又称为转移表:提供函数跳转功能)
6.1定义
存放函数指针的数组
6.2定义格式
以之前四个函数为例:
int ( * arr[4])( int, int) = { Add, Sub, Mul, Div}; //arr就是函数指针数组
6.3示例(仍以上文计算器为例)
#include <stdio.h>
int Add(int x, int y) {
return x + y;
}
int Sub(int x, int y) {
return x - y;
}
int Mul(int x, int y) {
return x * y;
}
int Div(int x, int y) {
return x / y;
}
void Mule() {
printf("***********************\n");
printf("****1.加法 2.减法****\n");
printf("****3.乘法 4.除法****\n");
printf("**** 0.退出 *****\n");
}
int main() {
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*p[5])(int, int) = { 0, Add,Sub,Mul,Div }; //保证以后扩充代码时
//可以不用switch语句进行扩充,方便修改,且使代码简洁
do {
Mule();
scanf("%d", &input);
if (input > 0 && input < 5) {
printf("请输入两个操作数->");
scanf("%d %d", &x, &y);
ret = p[input](x, y);
printf("%d\n", ret);
}
else if (input == 0) {
printf("退出计算器\n");
}
else {
printf("输入错误\n");
}
} while (input);
return 0;
}
7.指向函数指针数组的指针
7.1定义格式
int ( * parr[4])( int, int) = { Add, Sub, Mul, Div}; //arr就是函数指针数组
int ( * (*pparr)[4])( int, int) = &parr; //指向函数指针数组的指针
8.回调函数
8.1定义
通过函数指针调用的函数。当把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
8.2经典案例(qsort库函数,在中,默认升序)
void qsort (void* base, //需要排序的数组的首地址,不确定元素类型
size_t num, //需要排序的数组元素个数
size_t size, //每个元素的大小(字节数)
int (*compar)(const void*,const void*)); //自定义比较函数的指针,回调函数
//以上是qsort函数的声明及对每个形参的解释
//
int compar (const void* p1, const void* p2) //自定义比较函数定义
{
return (*(int *)p1 - *(int *)p2);
} //以整型元素为例
//void * 是无具体类型的指针,可以接受任意类型的地址
//但无法解引用和+-整数,因为这两个行为都需要知道字节数
//所以需要采用强制类型转换,把void *类型强制转换成其他类型
//也就是需要用户自己定义的原因
//
//以结构体类型元素为例
typedef struct Stu {
char name[20];
int age;
}Stu;
int compar_name(const void* p1, const void* p2) //自定义比较函数定义
{
return strcmp(((Stu*)p1) -> name , ((Stu*)p2) -> name);
//注意操作符优先级顺序
//强制类型转换优先级小于->
} //比较名字
int compar_age(const void* p1, const void* p2) //自定义比较函数定义
{
return (((Stu*)p1)->age - ((Stu*)p2)->age); //注意操作符优先级顺序
//强制类型转换优先级小于->
} //比较年龄
int main() {
Stu stu[] = { {"张三",20},{"李四",21},{"王五",19} };
int i = 0;
qsort(stu, 3, sizeof(stu[0]), compar_name);
for (i = 0; i < 3; i++) {
printf("%s %d\n", stu[i].name,stu[i].age);
}
printf("\n");
qsort(stu, 3, sizeof(stu[0]), compar_age);
for (i = 0; i < 3; i++) {
printf("%s %d\n", stu[i].name, stu[i].age);
}
return 0;
}
//模拟qsort实现通用冒泡排序
#include <stdio.h>
#include <string.h>
#include <stdlib.h> //qsort库函数所在位置
//得到需要交换的两个元素的起始地址和元素大小,一个字节一个字节进行交换
void Swap(void * buf1,void * buf2,int size) {
char temp = 0;
int i = 0;
for (i = 0; i < size; i++) {
temp = *((char*)buf1 + i);
*((char*)buf1 + i) = *((char*)buf2 + i);
*((char*)buf2 + i) = temp;
}
}
//冒泡升序排序
void Bubble_sort(const void* base, const int count, const int size, int(*compar)(const void*, const void*)) {
int i = 0;
int j = 0;
int flag = 1;
//趟数
for (i = 0; i < count - 1; i++) {
//一趟比较的次数
for (j = 0; j < count - 1 - i; j++) {
//强制类型转换为指向一个字符的指针,操作数据
if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0) {
Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
flag = 0;
}
}
if (flag == 1)
break;
flag = 1;
}
}
int compar_int(const void* p1, const void* p2) //自定义比较函数定义
{
return (*(int*)p1 - *(int*)p2);
} //以整型元素为例
//void * 是无具体类型的指针,可以接受任意类型的地址
//但无法解引用和+-整数,因为这两个行为都需要知道字节数
//所以需要采用强制类型转换,把void *类型强制转换成其他类型
//也就是需要用户自己定义的原因
int compar_str(const void* p1, const void* p2) //自定义比较函数定义
{
return strcmp((char*)p1, (char*)p2);
}
typedef struct Stu {
char name[20];
int age;
}Stu;
int compar_name(const void* p1, const void* p2) //自定义比较函数定义
{
return strcmp(((Stu*)p1)->name, ((Stu*)p2)->name); //注意操作符优先级顺序
//强制类型转换优先级小于->
} //以结构体类型元素为例
int compar_age(const void* p1, const void* p2) //自定义比较函数定义
{
return (((Stu*)p1)->age - ((Stu*)p2)->age); //注意操作符优先级顺序
//强制类型转换优先级小于->
} //以结构体类型元素为例
void test1() {
int i = 0;
int num[10] = { 9,8,7,6,5,4,3,2,1,0 };
Bubble_sort(num, 10, 4, compar_int);
for (i = 0; i < 10; i++) {
printf("%d ", num[i]);
}
printf("\n");
}
void test2() {
int i = 0;
char str[3][10] = { "zhangsan","lisi","wangwu" };
Bubble_sort(str, sizeof(str) / sizeof(str[0]), sizeof(str[0]), compar_str);
for (i = 0; i < 3; i++) {
printf("%s ", str[i]);
}
printf("\n");
}
void test3() {
Stu stu[] = { {"张三",20},{"李四",21},{"王五",19} };
int i = 0;
Bubble_sort(stu, 3, sizeof(stu[0]), compar_name);
for (i = 0; i < 3; i++) {
printf("%s %d\n", stu[i].name, stu[i].age);
}
printf("\n");
Bubble_sort(stu, 3, sizeof(stu[0]), compar_age);
for (i = 0; i < 3; i++) {
printf("%s %d\n", stu[i].name, stu[i].age);
}
printf("\n");
}
int main() {
test1();
test2();
test3();
return 0;
}
9.指针和数组笔试题解析(32位操作系统)
9.1一维数组
int a[] = {1,2,3,4};
printf("%d\n" ,sizeof(a));
//打印16,整个数组的byte大小
printf("%d\n" ,sizeof(a+0));
//打印4,此时a既没有单独放在sizeof中,也没有&,就表示首地址,+0还是首地址,4byte
printf("%d\n" ,sizeof(*a));
//打印4,此时a仍没有单独放sizeof中,表示首地址,解引用后计算首元素的大小,int型4byte
printf("%d\n" ,sizeof(a+1));
//打印4,理由同第四行代码
printf("%d\n",sizeof(a[1]));
//打印4,得到第二个元素
printf("%d\n" ,sizeof(&a));
//打印4,取整个数组地址,指针大小,4byte
printf("%d\n" ,sizeof(*&a));
//打印16,整个数组大小
printf("%d\n" ,sizeof(&a+1));
//打印4,取地址+1仍是地址,4byte
printf("%d\n" ,sizeof(&a[0]));
//打印4,首元素地址,4byte
printf("%d\n",sizeof(&a[0]+1));
//打印4,第二个元素地址,4byte
9.2字符数组
char arr[] = { 'a' ,'b' ,'c' , 'd' , 'e','f’};
//单个写字符且数组未标明大小,不自动补\0
printf("%d\n", sizeof(arr));
//打印6,字符串长度为6byte
printf("%d\n", sizeof(arr + O));
//打印4,此时arr既没有单独放在sizeof中,也没有&,就表示首地址
//+0还是首地址,4byte
printf("%d\n", sizeof(*arr));
//打印1,此时arr仍没有单独放sizeof中,表示首地址
//解引用后计算首元素的大小,char型1byte
printf("%d\n", sizeof(arr[1]));
//打印1,表示第二个元素,char型1byte
printf("%d\n", sizeof(&arr));
//打印4,取整个数组的地址,地址4byte
printf("%d\n", sizeof(&arr + 1));
//打印4,指向arr数组之后的相同数组大小地址位置,同样4byte
printf("%d\n", sizeof(&arr[0] +1));
//打印4,表示arr数组第二个元素的地址
printf("%d\n",sizeof(arr[0] + 1));
//打印4,char型+int型发生整型提升,提升为int型,4byte
printf("%d\n", strlen(arr));
//随机数,数组内无\0,越界访问
printf("%d\n", strlen(arr + 0));
//随机数,与第18行结果相同,数组内无\0,越界访问
printf("%d\n", strlen(*arr));
//error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(arr[1]));
//error,strlen库函数参数为指针,98属于野指针,读取98的地址发生访问冲突
printf("%d\n", strlen(&arr));
//随机数,但与第18行代码结果相同,strlen访问时一个byte一个byte访问
printf("%d\n", strlen(&arr + 1));
//比第18行代码随机数少6,&arr+1直接指向了arr数组之后的位置,跳过了arr数组
printf("%d\n", strlen(&arr[0] + 1));
//比第18行代码随机数少1,&arr[0]+1指向了arr数组第二个元素的位置
char arr[] = "abcdef"; //直接赋值字符串,末尾补\0,7个字符长度
printf("%d\n",sizeof(arr));
//打印7,字符串长度为7byte
printf("%d\n", sizeof(arr + O));
//打印4,此时arr既没有单独放在sizeof中,也没有&,就表示首地址
//+0还是首地址,4byte
printf("%d\n", sizeof(*arr));
//打印1,此时arr仍没有单独放sizeof中,表示首地址
//解引用后计算首元素的大小,char型1byte
printf("%d\n", sizeof(arr[1]));
//打印1,表示第二个元素,char型1byte
printf("%d\n", sizeof(&arr));
//打印4,表示数组的地址,4byte
printf("%d\n", sizeof(&arr + 1));
//打印4,表示arr数组之后的地址
printf("%d\n", sizeof(&arr[0] +1));
//打印4,表示数组第二个元素地址
printf("%d\n", strlen(arr));
//打印6,传递字符串首元素的地址,字符串\0前的字符有6个
printf("%d\n", strlen(arr + 0));
//打印6,传递的还是字符串首元素地址
printf("%d\n", strlen(*arr));
//error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(arr[1]));
//error,strlen库函数参数为指针,98属于野指针,读取98的地址发生访问冲突
printf("%d\n", strlen(&arr));
//打印6,实参是数组的地址,但strlen函数计算时时一个byte一个byte计算
//相当于还是首元素的地址
printf("%d\n", strlen(&arr + 1));
//随机数,传递的是arr数组之后位置的地址,不确定之后的内容
printf("%d\n", strlen(&arr[0] +1));
//打印5,传递的是arr数组第二个元素的地址
char* p = "abcdef" ; //p作为字符指针,只保存了a的地址,放在栈区
//"abcdef\0"是常量字符串,放在常量区(只读数据区)
printf("%d\n",sizeof(p));
//打印4,p是指针,4byte(区别于字符数组)
printf("%d\n", sizeof(p + 1));
//打印4,p+1仍是指针,指向字符串第二个字符位置
printf("%d\n", sizeof(*p));
//打印1,表示字符'a'
printf("%d\n", sizeof(p[0]));
//打印1,表示字符'a'
printf("%d\n", sizeof(&p));
//打印4,表示p的地址,地址4byte
printf("%d\n", sizeof(&p + 1));
//打印4,&p是二级指针,&p+1会跳过栈区的四个字节
printf("%d\n", sizeof(&p[0] + 1));
//打印4,&p[0]表示'a'的地址,+1表示'b'的地址
printf("%d\n", strlen(p));
//打印6,实参是字符串首地址
printf("%d\n", strlen(p + 1));
//打印5,p+1表示'b'的地址
printf("%d\n", strlen(*p));
//error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(p[0]));
//error,strlen库函数参数为指针,97属于野指针,读取97的地址发生访问冲突
printf("%d\n", strlen(&p));
//随机数,&p是栈区内p的地址,而栈区内的数据我们无法得知
printf("%d\n", strlen(&p + 1));
//随机数,&p+1是栈区内p之后四个byte的地址,而栈区内的数据我们无法得知
//且&p和&p+1之间有四个byte,无从得知这四个字节中是否存在\0
//如果存在在什么位置,所以这两个随机数的关系无法确认
printf("%d\n", strlen(&p[0] +1));
//打印5,实参是字符串第二个字符地址
9.3二维数组
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", sizeof(a[0] +1));
//打印4,由于a[0]不是单独的,有了+1后a[0]就表示第一行第一个元素的地址
//a[0]+1表示第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));
//打印4,表示第一行第二个元素
printf("%d\n", sizeof(a + 1));
重点//打印4,a没有单独存放,所以表示第一行的地址
//+1后就表示第二行的地址,是地址,不是第二行
printf("%d\n", sizeof(*(a + 1)));
重点//打印16,a+1表示第二行的数组地址,解引用后表示第二行
printf("%d\n", sizeof(&a[0] +1));
重点//打印4,a[0]表示第一行的数组名,&a[0]就表示首元素的地址
//+1后表示第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));
重点//打印16,与上一行代码相比,解引用后表示第二行
printf("%d\n", sizeof(*a));
//打印16,a不是单独出现,所以a表示首元素的地址,即第一行数组的地址
//解引用后表示第一行
printf("%d\n", sizeof(a[3]));
重点//打印16,sizeof不访问内容,它只拿到传递的实参的类型
//而a[3]的类型是int [4]
10.指针笔试题
笔试题1:
int main()
{
int a[5] = {5, 4, 3, 2, 1};
int *ptr = (int *)(&a + 1); //&a+1跳过整个数组,指向1的后面,强制类型转换后-1只移动四个byte
printf( "%d,%d", *(a + 1), *(ptr - 1)); //一个指向第二个元素地址,一个指向最后一个元素地址
return 0;
}
笔试题2:
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0×100000; //结构体*表示指向该结构体变量的指针变量类型
//假设p 的值为日x100000。如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
//x86
//
int main()
{
printf("%p\n", p + 0x1); //0x100000->0x100014
//0x1表示十六进制的1,0x100000+1,跳过一个结构体类型的大小,20byte
printf("%p\n",(unsigned long)p+0x1); //0x100000->0x100001
//强制类型转换为unsigned long类型,+1后跳过一个byte
printf("%p\n",(unsigned int*)p + 0x1); //0x100000->0x100004
//强制类型转换为unsigned int*类型,+1后跳过4个byte
return 0;
}
笔试题3:
int main()
{
int a[4] = { 1,2,3,4 }; //栈区内数组地址由低到高
//0x00000001,0x00000002,小端存储0x02000000
int* ptr1 = (int*)(&a + 1); //ptr1指向了4之后的地址
int* ptr2 = (int*)((int)a + 1); //(int)a+1是把首地址强制转换为整型再+1
//再强制转换为int *,相当于向后移动一个字节后的地址
printf("%×,%x", ptr1[-1],*ptr2);
//ptr1[-1]就表示4(0x00000004),*ptr2就表示0x02000000
return 0;
}
笔试题4:
int main(){
int a[3][2]= { (0,1),(2,3),(4,5)}; //小括号括起来里面表示逗号表达式
//元素为1,3,5,0,0,0
int* p;
p = a[0]; //a[0]没有取地址,也没有放在sizeof内部,就表示首元素的地址
//即把第一行首元素的地址赋值给p
printf("%d", p[0]); //打印第一行首元素的值
return 0;
}
笔试题5:
int main(){
int a[5][5];
int(*p)[4];
p = a; //强制类型转换后p[1][0] == a[0][4]
printf("%p,%d\n",&p[4][2]-&a[4]2],&p[4][2]-&a[4][2]);
//p[4][2] == 4*4+3 == 19 第19个元素
//p[4][2] == 4*5+3 == 23 第23个元素
//二者相减 == -4个元素
//%p == 11111111111111111111111111111100 == FF FF FF FC
//%d == -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);
//&aa+1直接跳过了整个数组,ptr1指向了数组后的地址
int* ptr2 = (int*)(*(aa + 1));
//aa+1表示首元素的地址+1,即表示第二行的地址,解引用之后表示第二行
//因为没有放在sizeof里面,也没有&,ptr2就表示第二行第一个元素的地址
printf("%d,%d",*(ptr1 - 1),*(ptr2 - 1));
//打印10,5
return 0;
}
笔试题7:
int main( )
{
char* a[] = { "work" ,"at" , "alibaba" };
char** pa = a;
//无sizeof,无&,表示首元素地址
pa++;
//at的指针的地址
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);
//++cpp表示c+2的地址,第一次*得到c+2,即POINT的地址,第二次*得到POINT
//++cpp对cpp产生影响,现在cpp指向了cp+1的位置
printf("%s\n",*-- * ++cpp + 3);
//++cpp导致cpp指向cp+2的位置
//解引用后得到c+1,再--表示c+1变成c,指向了c的位置
//再解引用后+3指向了E的位置,打印ER
printf("%s\n", *cpp[-2] + 3);
//先[-2],得到C+3,解引用得到FIRST,+3后打印ST
printf("%s\n", cpp[-1][-1] + 1);
//cpp[-1]得到c+2,c+2[-1]表示*(c+2-1) == c[1],+1后打印EW
return 0;
}
做题时画图更易理解!!!