C语言:指针(第三天)
字符数组和字符指针
字符串实现
在C语言中,表示一个字符串有以下两种形式:
- 用字符数组存放一个字符串。
- 用字符指针指向一个字符串。
案例
#include <stdio.h>
/**
* 方式1:使用字符数组实现字符串
*/
void str_test1()
{
// 定义一个伪字符串
char str[] = "I Love You";
printf("%s\n",str);
}
/**
* 方式2:使用字符指针实现字符串
*/
void str_tewt2()
{
// 定义一个伪字符串
char *str = "I Love You";
printf("%s\n",str);
}
int main(int argc,char *argv[])
{
str_test1();
str_tewt2();
return 0;
}
注意:字符数组和字符指针变量都能实现字符串的存储与运算。(字符指针–> 字符类型的指针变量)
字符数组和字符指针的联系
-
字符数组由元素组成,每个元素中存放一个字符,而字符指针变量中存放的是地址,也能作为函数参数。
-
只能对字符数组中的各个元素赋值,而不能用赋值语句对整个字符数组赋值。
char arr[3]; arr[2] = 'A';// 正确,对字符数组中的元素赋值 arr = {'A','B','C'};// 错误,(可以理解为数组名就是一个常量,一旦创建就不能再改变)
-
字符数组名虽然代表地址,但数组名的值不能改变,因为数组名是常量。
-
对于字符串中字符的存取,可以用下标法,也可以用指针。
案例
#include <stdio.h> /** * 方式1:使用字符数组实现字符串 */ int main(int argc,char *argv[]) { // 使用两种方式定义字符串 char str1[] = "你好,张欣!"; char *str2 = "你好,张欣!";// 我们将数据类型为char的指针变量称为字符指针 // 测试赋值 // str1 = "您好,张鹏!";// 不能对数组整体赋值,如果要赋值,请使用string库下的strcpy() str2 = "您好,张鹏!"; printf("%s,%s\n",str1,str2); return 0; }
字符串作为形式参数
-
实参与形参都可以是字符数组
void fun(char str[],int len){..} void main() { char str[] = "hello"; fun(str,sizeof(str) / sizeof(str[0])); }
-
实参用字符数组,形参用字符指针
void fun(char *str,int len){ str[2] = 'A';// Gcc编译环境可通过 } void main() { char str[] = "hello";// 常量池,此时的赋值,将常量池中的数据读取出来,存入到栈中数组对应的位置 fun(str,sizeof(str) / sizeof(str[0])); }
-
实参和形参都是指针变量(在函数内部不能对字符串中的字符做修改)
void fun(char *str,int len){ str[2] = 'A';错误,字符串常量一旦创建,就不能改变 } void main() { char *str = "hello"; fun(str,sizeof(str) / sizeof(str[0])); }
-
实参是指针类型,形参是字符数组(在函数内部不能对字符串中的字符做修改)
void fun(char str[],int len){ str[2] = 'A';错误,字符串常量一旦创建,就不能改变 } void main() { char *str = "hello"; fun(str,sizeof(str) / sizeof(str[0])); }
注意:
- 字符数组在创建的时候,会在内存中开辟内存空间,内存空间可以存放字符数据;字符指针在创建的时候,需要依赖于字符数组,字符指针在内存开辟的内存空间中,存放的是数组元素的地址。字符指针依的创建依赖于字符数组,字符数组可以独立存在,而字符指针不能独立存在。
- 字符数组可以初始化,但是不能赋值;字符指针可以初始化,也可以赋值。
案例
#include <stdio.h>
/**
* 字符指针作为函数参数:用函数调用实现字符串的复制以及长度计算。
* 定义一个函数,实现字符串的拷贝,返回字符串的长度
* @param source 拷贝的源字符串
* @param target 需要保存拷贝数据的目标字符串
* @return 字符串的大小
*/
int str_copy(char *source,char *target)
{
// 定义一个循环变量
int i = 0;
while(source[i] != '\0')
{
// 实现拷贝
*(target+i) = *(source+i);// 指针法
// target[i] = source[i];下标法
i++;
}
// 拷贝结束后,一定要给target末尾加上\0
target[i] = '\0';
return i;
}
int main()
{
// 定义两个数组,从键盘录入字符串
char source[20],target[20];
printf("请输入一个字符串:\n");
scanf("%s",source);
int len = str_copy(source,target);
printf("%s,%s,%d\n",source,target,len);
return 0;
}
案例
/**
* 字符指针作为函数的参数,给定一个字符串,截取start到end之间的字符串,含头不含尾
* 定义一个函数,实现字符串的截取
* @param source 源字符串
* @param start 开始截取的位置
* @param end 截取结束的位置
* @param target 截取后的字符串
* @return 新字符串的长度
*/
#include <stdio.h>
int str_split(char *source,int start,int end,char *target)
{
// 定义一个循环变量
int i = 0,k = 0;
// 遍历源字符串(数组)
while(source[i] != '\0')
{
// 根据位置截取
if(i >= start && i < end)
{
// 将截取的字符串存入target
target[k] = source[i];
k++;
}
i++;
}
// 新字符串需要末尾添加\0
target[k] = '\0';
return k;
}
int main()
{
char *str = "abcdefg";
char target[100];
int len = str_split(str,2,5,target);
printf("%s,%s,%s\n",str,target,len);
return 0;
}
函数指针与指针函数
函数指针
定义:函数指针本质上是指针,它是函数的指针(定义了一个指针变量,变量中存储了函数的地址)。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。这里函数名就代表入口地址。
函数指针存在的意义:
- 让函数多了一种调用方式
- 函数指针作为形参,可以形式调用(回调函数)
定义格式:
返回值类型 (*变量名)(形式参数列表);
举例:
int (*p)(int a,int b);
函数指针的初始化:
-
定义的同时赋值
// 函数指针需要依赖于函数,先有函数,再有指针 // 定义一个普通的函数 int add(int a,int b){ return a +b; } // 定义一个函数指针,并给他赋值 // 通过以下代码我们发现:函数指针的返回类型和依赖函数的返回类型一致,函数指针的参数个数类型和依赖函数一致 int (*p)(int a,int b) = add;// 赋值一定要注意:函数不能带有()小括号
-
先定义后赋值
// 定义一个普通的函数 int add(int a,int b){ return a +b; } // 定义一个函数指针 int (*p)(int,int);// 一般写作这种 // 给函数指针赋值 p = add;
注意:
- 函数指针指向的函数要和函数指针定义的返回值类型,形参列表对应,否则编译报错
- 函数指针是指针,但不能指针运算,如p++等,没有实际意义
- 函数指针作为形参,可以形成回调
- 函数指针作为形参,函数调用时的实参只能是与之对应的函数名,不能带小括号
- 函数指针的形参列表中的变量名可以省略
案例
/**
* 函数指针案例,指向函数的指针变量就是函数指针
* 求a,b两个数的最大值
* 定义一个函数指针
*/
#include <stdio.h>
int max(int a,int b)
{
if(a>b)
return a;
return b;
}
int main(int argc,char *argv[])
{
// 定义测试数据
int a = 3,b = 2,c;
// 直接函数调用
c = max(a,b);
printf("%d,%d两个数中的最大值是:%d\n",a,b,c);
// 定义一个函数指针
int (*p)(int,int) = max;
// 间接函数调用
c = p(a,b);
printf("%d,%d两个数中的最大值是:%d\n",a,b,c);
c = (*p)(a,b);
printf("%d,%d两个数中的最大值是:%d\n",a,b,c);
return 0;
}
回调函数(了解)
概念
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
为什么要用回调函数
- 因为可以把调用者和被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。
- 简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法
实现
/**
* 函数指针的特殊用法-回调函数
* 回调函数1
*/
int callback_1(int a)
{
printf("hello,this is callback_1:a=%d\n",a);
return 0;
}
/**
* 回调函数2
*/
int callback_2(int b)
{
printf("hello,this is callback_2:b=%d\n",)b;
}
/**
* 实现回调函数
*/
int handle(int x,int (*callback)(int))
{
printf("开始执行!\n");
callback(x);
printf("执行结束!\n");
}
int main(int argc,char *argv[])
{
handle(100,callback_1);
handle(200,callback_2);
return 0;
}
指针函数
定义:本质上是函数,这个函数的返回值类型是指针,这个函数称为指针函数。(应用场景:需要返回数组的时候)
语法:
指针类型 函数名(形参列表)
{
函数体;
return 指针变量;
}
举例:
// int *get(int a)
int* get(int a)
{
int *b = &a;
return b;
}
注意:
在函数中不要直接返回一个局部变量的地址,因为函数调用完毕后,局部变量会被回收,使得返回的地址就不明确。
解决方案:
如果非要访问,可以给这个局部变量添加static
,可以延长它的生命周期,从而避免野指针(尽量少用,因为存在内存泄漏)
/**
* 指针函数-有若干个学生的成绩(每个学生有4门成绩),要求在用户输入学生序号后,能输出该学生的全部成绩,用指针函数来实现
*/
#include <stdio.h>
/**
* 定义一个函数,传入学生的序号,返回这个学生的所有课程成绩
* @param p 二维数组
* @param n 学生索引(二维数组中的行号)
* @return 学生成绩(行号对应的列数组)
*/
float *search(float (*p)[4],int n)
{
// 定义一个指针,用来接收查询到的某个学生的所有课程
float *pt;
pt = *(p+n) // *(p+n),*p[n],p[n]
return pt;
}
int main()
{
// 准备一个二维数组
float score[][4] = {60,70,80,89},{55,66,77,88},{90,89,90,91};
int m;
float *p;
printf("请输入学生序号(0~2):\n");
scanf("%d",&m);
printf("第%d个学生的成绩:\n",m);
// 用来接收某个学生的所有成绩
p = search(score,m);
// 遍历成绩
for(int i = 0,i < 4,i++)
{
printf("%5.2f\t",*(p+i));
}
printf("\n");
return 0;
}
二级指针
说明:指针除了一级指针,还有多级指针,但是我们一般开发中最多用到二级指针。三级指针本质上用法和二级指针差不多。
定义:二级指针,又被称之为多重指针,引用一级指针的地址,此时这个指针变量就得定义成二级指针。
int a = 10; // 普通变量
int *p = &a; // 一级指针
int **w = &p; // 二级指针
int ***x = &w;// 三级指针
定义格式:
数据类型 **变量名 = 指针数组的数组名或者一级指针的地址
举例:
// 指针数组
int arr = {11,22,33};
int *arr_ = {&arr[0],&arr[1],&arr[2]};
// 一级指针
int a = 10;
int *p = &a;
// 二级指针和指针数组
// 字符型指针数组,本质上是一个二维的char数组
char *str[3] = {"abc","aaa034","12a12"};
// 如果要用一个指针变量来接收,就需要一个二级指针
char **p_ = str;// str指向的是首元素的地址
// 二级指针和二维数组
int arr[2][3] = {{1,2,3},{11,22,33}};
int **k = arr;// 编译报错,数据类型不相符(二维数组不等于二级指针)
int a = 90;// 变量
int *p = &a;// p存放a地址
int **k = &p;// k存放p的地址
printf("%p,%p,%d\n",k,*k,**k);// p->p的地址,*k->p的值->a的地址,**k->a的地址-a的值
结论:
- 二级指针和指针数组是等效,和二维数组不等效。
- 二维数组和数组指针是等效,和二级指针不等效。
二级指针的用法:
- 如果是字符的二级指针,可以像遍历字符串数组一样遍历它
- 如果是其他的二级指针,就需要解引用两次访问它所指向的数据
案例
/**
* 二级指针案例:使用指向指针数组的指针变量。
*/
#include <stdio.h>
void fun1()
{
// 字符指针
char *name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"};
// 定义一个二级指针(字符的二级指针)
char **p;
// 定义循环变量
int i = 0;
// 遍历指针数组
do
{
p = name + i;
printf("%s\n", *p);
i++;
} while (i < 5);
printf("\n");
}
void fun2()
{
int arr1[5] = {11, 12, 13, 14, 15};
// 创建一个指针数组
int *arr[] = {&arr1[0], &arr1[1], &arr1[2], &arr1[3], &arr1[4]};
int **p = arr, i = 0;
// 遍历
for (; i < 5; i++)
{
// printf("%5d",**(p+i));
printf("%5d", **p);
p++;
}
printf("\n");
}
int main()
{
fun1();
fun2();
}