字符指针
字符指针即可以指向单个字符,也可以指向一个字符串,一般情况下,我们更常将字符指针应用于指向一个字符串;我们要注意区分字符指针与字符数组的区别,这两个经常在使用的时候被混淆;
代码char* pstr = "hello world";
特别容易让我们以为是把字符串hello world
放到字符指针 pstr 里了,但是本质是把字符串hello world
首字符的地址放到了 pstr 中;
代码char pstr[] = "hello world";
这个代码才是将字符串hello world
放进了 pstr 字符数组中了;
举个例子让大家感受一下这二者的区别:
#include <stdio.h>
#include<stdlib.h>
int main(){
char str1[] = "HelloWorld";
char str2[] = "HelloWorld";
char* str3 = "HelloWorld";
char* str4 = "HelloWorld";
if(str1 == str2){
printf("str1 和 str2 相同!\n");
}
else{
printf("str1 和 str2 不相同!\n");
}
if(str3 == str4) {
printf("str3 和 str4 相同!\n");
}
else {
printf("str3 和 str4 不相同!\n");
}
return 0;
}
//输出结果:
//str1 和 str2 不相同!
//str3 和 str4 相同!
由此可见对于字符数组 str1,str2 是由两块不同的内存空间进行存储,这里 str3 和 str4 指向的是同一个常量字符串。c/c++ 会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候 就会开辟出不同的内存块。所以 str1 和 str2 不同,str3 和 str4 相同。
指针数组和数组指针
指针数组
字面上来看,这是一个数组,不过数组中的每一个元素都是一个指针,书写格式:typeName* arrayName[N];
//例:
int* pstrArr[10]={NULL};
上面这个不难理解,很简单,有了这个基础之后就可以写出下面的有实际用途的指针数组:
char* array[4];
//字符指针数组,里面的每个元素都是一个字符指针,可以用来指向字符或者字符串
//这种指向方式更为简单节省空间,而当我们使用二维数组存储多个字符串往往需要花费更多空间
char** array[5];
//二级字符指针数组,里面的每个元素都是一个指向字符指针的指针,这种用法很不多见,也较为复杂
//但是在我们想要指向多个字符指针数组时就派的上用场。
数组指针
字面看来,这是一个指针,指向一个数组,书写格式:typeName (*arrayName)[N];
//例:
int (*pstrArr)[10]=NULL;
当我们定义了一个数组指针后,我么可以将任意数组取地址赋给它:
#include <stdio.h>
#include<stdlib.h>
int main()
{
int array[4] = {0};
int (*arrayPtr)[4] = &array;//注意数组指针定义时[]中的值需与目标数组的长度相同
printf("%p\t%p\n", &array, arrayPtr);
return 0;
}
//输出:
//0029FDC8 0029FDC8
数组名 vs &数组名
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
//输出:
//arr = 00F6FAD4
//&arr= 00F6FAD4
//arr+1 = 00F6FAD8
//&arr+1= 00F6FAFC
根据上面的代码我们发现,其实&arr
和arr
,虽然值是一样的,但是意义是不一样的。 实际上:&arr
表示的是数组的地址,而不是数组首元素的地址;arr
表示的是数组首元素的地址。数组的地址 + 1,跳过整个数组的大小,所以&arr+1
相对于&arr
的差值是 40;而数组首元素的地址 + 1,跳过的是一个数组元素的大小。
同时在这里还需强调的一点是,有时尽管指针的值相同,也就是说指向同一块内存地址,却有可能指针类型完全不同。
#include <stdio.h>
int main()
{
int arr[3][4] = {0};
printf("%p\n", arr);//二维数组首地址,一维数组指针int (*)[4]
printf("%p\n", &arr);//二维数组指针int (*)[3][4]
printf("%p\n", arr[0]);//一维数组首地址,int型指针int*
printf("%p\n", &arr[0][0]);//二维数组第一个元素的地址,int型指针int*
return 0;
}
//输出:
//006FF844
//006FF844
//006FF844
//006FF844
从上面的代码看出,在我们平时写代码时,有时虽然指针类型不同,但是他们指向同一块内存地址,所以我们在对指针进行操作时,一定要清楚指针的类型,千万不要出错了。
下面写一个数组指针的使用:
#include <stdio.h>
#include<stdlib.h>
void print_arr1(int arr[3][5], int row, int col){
int i = 0;
for(i=0; i<row; i++){
for(j=0;j<col;j++){
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5], int row, int col){
int i = 0;
for(i=0; i<row; i++){
for(j=0; j<col; j++){
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main(){
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
拓展:数组指针数组;
int (*ptr[10])[4];
以上这行代码表示定义了一个长度为 10 的数组,数组内每一个元素都是一个数组指针,其中每个指针指向长度为 4 的int
型数组。有了数组指针数组,那么是否就有指针数组指针呢?是的,得益于C语言中灵活多变的语法,这些都可以得到实现,但是难度较高也并不经常使用,在此不再讨论。
指针与数组传参
在C语言中,我们都知道对于函数传递参数都是进行副本传参,也就是将实参克隆一份变为形参再进行传递,因此形参的改变并不会影响到实参。但是当我们传递指针的时候就可以对原本指针所指向的变量进行改变。而当我们想要把数组传递进函数中时,无论我们以何种语法进行传递,在C语言中数组都会被隐式转换为指针进行传递。下面我们来看看实例操作;
一维数组传参
#include <stdio.h>
//直接使用指针进行传入
void Print(int* arr, int len)
{
for(int i = 0; i < len; i++)
{
printf("%d\n", arr[i]);
}
}
int main()
{
int arr[4] = {1, 2, 3, 4};
Print(arr, 4);
return 0;
}
#include <stdio.h>
//使用数组的方式进行传入,这里[]中的大小可以省略
void Print(int arr[], int len)
{
for(int i = 0; i < len; i++)
{
printf("%d\n", arr[i]);
}
}
int main()
{
int arr[4] = {1, 2, 3, 4};
Print(arr, 4);
return 0;
}
对于一维数组来说,我们可以直接使用指针的方式进行传入,也可以使用数组作为形参进行传入;因为数组作为参数传入的时候会隐式转换为指针,所以作为参数传入的数组无法求得数组长度,因此,我们必须在数组传入之前就求得数组长度,然后也作为一个参数传入,这样就拥有了数组的大小。
二维数组传参
#include <stdio.h>
//以数组形式传入,可以省略行数,但不能省略列数
void Print(int arr[][4], int len)
{
for(int i = 0; i < len; i++)
{
for(int j = 0 ; j < 4; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
Print(arr, 3);
return 0;
}
#include <stdio.h>
//以函数指针的形式传入,需要标明每个一维数组中有几个元素
void Print(int (*arr)[4], int len)
{
for(int i = 0; i < len; i++)
{
for(int j = 0 ; j < 4; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
Print(arr, 3);
return 0;
}
对于二维数组来说,也同样的有两种方法,一种是以数组的方式,另一种是以与其相同类型的数组指针的方式,不过这里不同的是,二维数组的低维度不可省略,而高维度可以省略。
数组名 arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的 arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收;
-
总结:
- 在使用数组传参的时候,一维数组、二维数组都可以都可以使用数组形式接受传参;一维数组、二维数组在当做参数传入的时候,都会隐式转化为指针形式,所以我么可以使用指针/函数指针来接受传参。
一级指针传参
#include <stdio.h>
void print(int *p, int sz){
int i = 0;
for(i=0; i<sz; i++) {
printf("%d\n", *(p+i));
}
}
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz= sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
二级指针传参
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
一级指针、二级指针传参,没有什么太难的地方,只需要掌握传入的实参和函数的形参保证是同一个类型即可。因为指针很灵活,所以有些写法看似差异很大,但实际是一个类型,有些看着相似,但其实类型不同,所以只要克服这点,就没什么问题了。例如:
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
//这个看起来不像同一个一个类型,但是其实是对的,arr是数组名,是数组首元素地址
//而这个数组是指针数组,所以它的每个元素都是一个指针,所以这也是二级指针
test(arr);
return 0;
}
函数指针
函数指针,顾名思义就是一个指向函数的指针,它的语法很简单:
函数返回值类型 (* 指针变量名) (函数参数列表);
而我们想要函数指针指向某个函数,则只需要让它指向和它返回值类型以及参数完全相同的函数即可。当他进行指向后我们就可以使用函数指针直接进行函数的调用。
#include <stdio.h>
void Print(int (*arr)[4], int len)
{
for(int i = 0; i < len; i++)
{
for(int j = 0 ; j < 4; j++)
{
printf("%d\t", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
Print(arr, 3);
void (*ptr)(int (*arr)[4], int len) = Print;
ptr(arr, 3);
(*ptr)(arr, 3);
//以上三种调用方式完全等价
return 0;
}
在以上的例子中我们使用了一个 ptr 函数指针指向了Print()
函数,注意在给函数指针赋值时,函数指针名就是函数的地址了,而在使用函数指针进行函数调用时,则可以直接对指针像函数呢样传参即可,当然也可以先解引用再传参,都是等价的。
当然,如果我们要定义几个相同函数指针的时候,我们就可以使用typedef
来简化我们的书写,例如:
#include <stdio.h>
void change(int* a, int* b) {
int t = 0;
t = *a;
*a = *b;
*b = t;
}
int main(){
int a = 10;
int b = 20;
int c = 30;
typedef void (*cmp)(int*, int*);
cmp p1 = change;
cmp p2 = change;
p1(&a, &b);
p2(&b, &c);
printf("a=%d\nb=%d\nc=%d\n", a, b, c);
return 0;
}
//输出:
//a=20
//b=30
//c=10
函数指针数组
可以定义一个数组,数组中的每一个元素都是一个指针,指向对应类型的函数,不过数组中的函数指针都是同一类型的,这也是它的缺陷,很单调;虽然如此,它的作用还是很大的,下面举一个例子,计算器的应用:
#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;
}
指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针 ;
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[10])(const char*) = &pfunArr;
return 0;
}
这种指针的使用很少,大家了解一下就好。
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
使用回调函数,模拟实现qsort(采用冒泡的方式):
#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;
}
使用回调函数,来实现简单的计算器函数回调:
#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {NULL};
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 b ? a / b : -1;
}
/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{
switch (op)
{
case '+':
return add;
case '-':
return sub;
case '*':
return mul;
case '/':
return div;
default:
return NULL;
}
return NULL;
}
int main(void)
{
/*无论是什么类型的指针,在32位系统下都占4字节*/
printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));
/*实现函数回调*/
printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));
printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));
printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));
printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));
return 0;
}