目录
1.字符指针(char*)
上面的代码char* ps前加上const修饰更加严谨
将字符串"abcdef"的首地址放到ps中,"abcdef"是常量字符串不能被修改
char arr[ ]=" abcdef ";
char* p=arr;//p指向arr的起始位置
*p=‘w’;//对p进行解引用,其实就是数组的第一个元素
p指向的是数组的首元素,arr数组是可以被修改
int main()
{
char str1[] = "hello CSDN.";
char str2[] = "hello CSDN.";
const char* str3 = "hello CSDN.";
const 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 str 4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
str1和str2是两个独立的数组
str3和str4中的hello CSDN.是常量字符串,常量字符串不能被修改,所以这个常量字符串没必要存放两份,只存一份就够(内存优化)
如果要比较字符串的内容,使用:strcmp
2.指针数组
指针数组的本质是数组,里面存的是指针
int * arr1[ 10 ]; / /整形指针数组
char* arr2[ 4 ]; / /一级字符指针数组
char** arr3[ 5 ]; / /二级字符指针数组
用指针数组模拟实现二维数组
3.数组指针
3.1数组指针的定义
数组指针是指针
去掉名字就是它的类型
这里的(*p)就相当于arr
一般情况下,数组指针应用于二维数组
数组指针的应用
p+i指向第i行的地址
二维数组的数组名,也表示数组首元素的地址,二维数组的首元素是第一行,首元素的地址就是第一行的地址,是一维数组的地址
*(p+0)-->p[0]-->&p[0][0]
3.2 &数组名和数组名
数组名是数组首元素的地址
有两个例外:
- 1.sizeof(数组名)
- 2.&数组名
int arr [ 5 ] ; / /整型数组
int *parr1 [ 10 ] ; / /指针数组
int ( *parr2 )[10 ] ; / /数组指针
int ( *parr3 [10 ])[ 5 ] ; parr3 是存放数组指针的数组
/ /将名字parr3 [ 10 ] 去掉
/ /int (*) [ 5 ] - ->数组的元素类型
4.数组参数、指针参数
4.1一维数组传参
#include<stdio.h>
void test ( int arr [ ])
{}
void test ( int arr[ 10 ])
{}
void test ( int *arr )
{}
void test2 ( int * arr[ 20 ])
{}
void test2 ( int **arr){}
int main()
{
int arr[ 10 ]={ 0 };
int *arr2[20]={ 0 };
test2(arr2);
}
/ /以上写法均可以
4.2二维数组传参
void test ( int arr [ 3 ] [ 5 ] ) / / ok
{ }
void test ( int arr [ ] [ ] )
{ }
void test ( int arr [ ] [ 5 ] ) / / ok
{ }
//总结:二维数组传参,函数形参的设计只能省略第一个[ ]的数字
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少个元素
//这样才方便运算
void test ( int* arr )
{ } / /二维数组名是数组第一行的地址,而这个函数是整型指针,所以不行
void test ( int* arr [ 5 ] )
{ } / /二维数组名是数组首元素地址,而这个函数的参数是数组,所以不行
void test(int(*arr)[5])/ / ok
{ }
void test ( int ** arr )
{ } / /传过来是一维数组的地址(一行的地址),二级指针是用来接收一级指针变量的地址
int main( )
{
int arr [ 3 ] [ 5 ] = { 0 } ;
test ( arr ) ;
}
/ / 二维数组传参,函数形参的设计只能省略第一个[ ]的数字。
/ / 因为对一个二维数组,可以不知道有多少行
/ / 但是必须知道一行多少元素,这样才方便运算
二维数组传参
参数可以是指针,也可以是数组
如果是数组,行可以省略,但是列不可以省略
如果是指针,传过去的是第一行的地址,形参就应该是数组指针
4.3一级指针传参
一级指针p
当一个函数的参数部分位一级指针的时候 ,函数能接收什么参数?
int a;
print(&a,10);
int*p1=&a;
print(p1,10);
int arr[10];
print(arr,10);
当函数的参数为二级指针的时候,可以接受什么参数?
void test(char **p)
{
}
int main()
{
char c='b';
char*pc=&c;
char**ppc=&pc;
char*arr[10];
test(&pc);
test(ppc);
test(arr);
return 0;
}
5.函数指针
整型指针-指向整型的指针 int *
字符指针-指向字符的指针 char*
数组指针-指向数组的指针 int arr[10]; int (*p)[10]=&arr;【数组指针:存放数组地址的指针】
函数指针-指向函数的指针【函数指针:存放函数地址的指针】
函数名和&函数名都是取函数的地址
函数名就是函数的地址,无论有没有&都是函数的地址,没有区别
此时pf就是函数指针变量
/ /代码1
( * ( void ( * ) ( ) ) 0 ) ( )
将0强制类型转换为void(*)( )--->把0当作一个函数的地址
这就意味着0地址处放着一个函数,函数没有参数,返回类型是void
然后解引用0的地址,找0地址处的函数--->调用该地址处的函数
上面的代码是一次函数调用
把0直接转换一个void(*)()的函数指针,然后去调用0地址处的函数
/ /代码2
void ( *signal ( int ,void(*)(int) ) ) (int);
这里是函数声明,没有传参数,不是函数调用,函数的定义需要有大括号,故不是函数的定义,但是又有函数的参数类型,所以这里是函数的声明
signal是函数名,signal的参数一个是int类型,另一个是void(*)(int)函数指针类型,然后这个signal函数的返回类型是void (*)(int)函数指针类型
简而言之:
上述代码是一次函数声明
声明的函数:signal
signal函数的第一个参数是int类型的
signal函数的第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
signal函数的返回类型也是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
注意:这个写法是固定的,不能这样写void(*) (int)signal (int , void(*)int))
这个代码看起来很繁琐,可用typedef重新命名一个名字,使这行代变得简洁
typedef void ( *pf_t )( int ) / /重新定义的类型名必须放在它的内部这是语法要求
/ /那么上面的代码可以写为
pf_t signal( int , pf_t );
typedef void(*pf_t2)(int);//pf_t2是类型名
void(*pf)(int);//pf是函数指针变量的名字
int mystrlen(const char* str)
{
//...
}
int main()
{
//指针数组
char* arr[10];
//数组指针
int arr2[5];
int(*p)[5]=&arr2;
//函数指针
int(*pf)(const char*)=my_strlen;//pf是一个指向函数的函数指针变量
(*pf)("abcdef");
pf("abcdef");
my_strleln("abcdef");
//上面三种方式调用都是可以的
}
6.函数指针数组
函数指针数组可以存放多个参数相同、返回类型相同的函数的地址
函数指针数组:数组的每个元素是一个函数指针
函数指针 int (*pf ) ( int , int )
函数指针数组 int ( *pfArr[ 4 ] ) ( 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");
}
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;
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
int x = 0;
int y = 0;
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
int(*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
int ret = (*pfArr[input])(x,y);
printf("%d\n", ret);
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
7.指向函数指针数组的指针
int Add(int x, int y)
{
return x + y;
}
int Sub(int x , int y)
{
return x - y;
}
int main()
{
int (*pf)(int , int)=Add;
//函数指针数组
int (*pfArr[4])(int ,int )={Add,Sub};
int (*(*ppfArr[4]))(int ,int )=&pfArr;//ppfArr是一个指向函数指针数组的指针变量
return 0;
}
8.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现直接调用,而是再特定的事件或条件发生时由另外的以方调用的,用于该事件或条件进行响应
此时的calc就是回调函数
冒泡排序的缺点: 只能排序整型的数据
而qsort函数能够排任意类型的数据
qsort函数的使用:
void qsort(void* base,size_t num,size_t witdh,int(*cmp)(const void* e1,const void* e2))
void*base--->待排序的数据首元素的地址,也就是指向起始位置
size_t num--->待排序数据的元素个数
size_t witdh--->待排序数据中每个元素的大小 (单位是字节)
int (*cmp)(const void*e1,const void *e2)--->比较两个元素的大小的函数指针
两个整型使用关系运算符比较大小
两个字符串用库函数strcmp比较大小
两个结构体需要指定方式进行比较(比如:一个人是按照名字进行比较还是用年龄进行比较)
由于比较方式的不同,比较方法单独分装一个函数
void*:
void*可以接受任意类型的指针,但是void*类型的指针不能++,因为不知道能跳过几个字节,不过,想要void*++,需要将void*类型的指针强制类型转换为其他类型的指针,并且解引用*,便可让它++
将冒泡排序改为可以排任意元素的排序(根据qsort函数)---->用冒泡排序模拟实现qsort函数
冒泡排序中的比较部分分装一个函数指针(因为qsort函数就是用函数指针)来比较大小