C语言 15 指针进阶 贰

简介: C语言 15 指针进阶 贰

1.字符指针

const关键字:

1.修饰局部变量,表示变量的值不能被改变了,用const修饰变量时,一定要给变量初始化,否则之后不能赋值

const 用于修饰常量字符串


2.常量指针与指针常量

指针常量指向的地址不能改变,但是地址中存储的值可以改变


3.修饰函数的参数


int main()
{
  char ch = 'a';
  char* pc = &ch;
  const char* ps = "abcdef";//将字符串首字符'a’的位置存放在指针中 ps指向字符串
  //ps存放的是字符串首字符'a'的地址
  //const可以修饰指针变量 使得指针存放的值不能被修改 用const ps维护指针更加安全
  return 0;
}

字符指针——指向字符的指针——存放字符的地址

char ch = 'a';

char* pc = &ch;


整形指针——指向整形的指针——存放整形的地址

int num = 10;

int* pa = #


数组指针——本质是指针——存放数组的地址

int arr[10] = { 0 };


*********int (*p)[10] = &arr;*********//拿到的是数组的地址

数组名的理解:

数组名绝大部分情况下,数组名是数组首元素的地址

1.sizeof (数组名),数组名表示整个数组,计算的是整数数组的大小

2.&数组名,数组名也表示整个数组,取出的是整个数组的地址


除此之外,所有的数组名都是数组首元素的地址!


二维数组传参的时候,传递的是数组首元素的地址,也就是第一行的地址,就可以使用数组指针来接收


3.指针数组

指针数组——本质是数组——是存放指针的数组

char* arr[5];//存放字符指针的数组

int* arr2[6];//存放整形指针的数组


使用数组指针来模拟一个二维数组:

//使用数组指针来模拟一个二维数组:
int main()
{
  int arr1[5] = { 1,2,3,4,5 };
  int arr2[5] = { 2,3,4,5,6 };
  int arr3[5] = { 3,4,5,6,7 };
  int* arr[3] = { arr1,arr2,arr3};
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

标答:

int main()
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
  //int* int* int*
  int* arr[] = { arr1,arr2,arr3 };
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
    {
      printf("%d ", arr[i][j]);//通过访问数组下标来访问每一个元素
    }
    printf("\n");
  }
  return 0;
}

4.数组传参和指针传参

数组传参的时候:

1.传递的是数组首元素的地址

1>一维数组传参,传递的是第一个元素的地址

2>二维数组传参,传递的是第一行的地址

2.数组传参的时候,形参可以写成数组名,也可以写成指针


举例:

形参是数组

test(int a[10]){}

test(int a[]){}


形参是指针

test(int *p){}


二维数组传参


int arr[10];

test(arr);

形参写成数组:test(int arr[3][5]){}

            test(int arr[][5]{}  行数可省略,列数不可省略

形参写成指针:test(int (*p)[5]){}//指针默认指向数组第一行


函数指针:函数指针:指向函数的指针 存放的是函数的地址 类比字符指针 整形指针 数组指针


int Add(int x, int y)
{
  return x + y;
}

&函数名和函数名 都是函数的地址

int main()
{
  printf("%p\n", &Add);//打印的是函数的地址 00007FF7CA5611CC
  printf("%p\n", Add);//打印的是函数的地址 00007FF6DEDB11CC
  int (*pf)(int, int) =  &Add;
//  返回类型   参数类型  函数名或取地址函数名
  printf("%p\n", pf);//pf是函数指针变量 打印的是函数的地址 00007FF6DEDB11CC
  int arr[10];
  int(*pa)[10] = &arr;//数组指针
  printf("%p\n", pa);//pa是数组指针变量 打印的是数组的地址 000000C3848FFAB8
  int ret = pf(3, 5);//可以通过函数指针直接调用函数  (*pf)(3, 5); *可有可无
  int ret2 = Add(3, 5);//也可以通过函数名直接调用函数
  printf("%d\n", ret);
  printf("%d\n", ret2);
  return 0;
} 

6.函数指针数组

函数指针数组:

char* arr[5] ;字符指针数组 数组 存放的是字符指针

int* arr2[6];  整形指针数组 数组 存放的是整形指针

                     函数指针数组 数组 存放的是函数指针

int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int main()
{
  int (*pf1)(int, int) = &Add;
  int (*pf2)(int,int) = &Sub;
  //数组中可以存放类型相同的多个元素
  int (* pfArr[4])(int, int) = { &Add, &Sub };//pfArr是函数指针数组 是存放函数指针的数组
  //数组有四个元素 每个元素都是存放一个地址的指针
  return 0;
}

函数指针数组的应用 函数指针数组的用途

模拟计算器

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;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    //创建一个函数指针数组
      //函数指针数组  转移表
    int (*pfarr[])(int, int) = { null,add,sub,mul,div };
    //                            0    1   2   3   4
    if (0 == input)
    {
      printf("退出计算器\n");
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入两个操作数\n");
      scanf("%d %d", &x, &y);
      ret = pfarr[input](x, y);
      printf("ret=%d\n", ret);
    }
    else
    {
      printf("选择错误,重新选择!\n");
    }
  } while (input);
  return 0;
}

7.指向函数指针数组的指针

int Add(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
int main()
{
  int a = 10;
  int b = 20;
  int c = 30;
  int* arr[] = { &a,&b,&c };//整型指针数组
  int* (*p)[3] = &arr;//p是指针,是指向整形指针数组的指针
  int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//pfArr是函数指针数组
  int(*(*p)[5])(int,int) = &pfArr;//p是存放函数指针数组的指针
  return 0;
}

8.回调函数

依赖函数指针,有函数指针才能实现回调函数,在另一个函数内部通过指针寻找该函数的地址,调用该函数

回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用,用于对该事件或条件进行响应


举例:

利用函数指针,实现回调函数,解决了代码的冗余

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;
}
void calc(int (*pf)(int, int))//函数指针
{
  int x = 0;
  int y = 0;
  int ret = 0;
  printf("请输入两个操作数\n");//没有直接调用函数,将地址传给这个函数,在这个函数内部调用
  scanf("%d %d", &x, &y);//这里的加减乘除就是回调函数
  ret = pf(x, y);//调用函数并返回
  printf("ret = %d\n", ret);
}
int main()
{
  int input = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    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:
      break;
    }
  } while (input);
  return 0;
}

回调函数案例:qsort函数

qsort是一个库函数

底层使用的是快速排序的方式,对数据进行排序

这个函数可以直接使用

这个函数可以用来排序任意类型的数据

对数据进行排序:

冒泡排序

快速排序

选择排序

插入排序

void bubble_sort(int arr[], int sz)
{
  int i = 0;
  //比较的趟数 n个元素要进行n-1次冒泡排序 n个元素有n-1对元素要进行排序
  for (i = 0; i < sz - 1; i++)
  {
    //每一趟冒泡排序
    int j = 0;
    for (j = 0; j < sz-1-i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        int t = 0;
        t = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = t;
      }
    }
  }
}
void print_arr(int arr[],int sz)//这个函数只能排序整形数据
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
int main()
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  print_arr(arr, sz);
  bubble_sort(arr, sz);//冒泡排序
  print_arr(arr, sz);
  return 0;
}

***************************qsort函数举例*****************************//

qsort函数的使用方法:

回忆冒泡排序的算法

给一组整形数据,使用冒泡排序算法,拍成升序

核心思想:相邻两组元素进行比较

n个元素要进行n-1次冒泡排序 n个元素有n-1对元素要进行排序

int cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;//强制转换为整形指针
  //compare函数返回值-1 0 1
}
void print_arr(int arr[], int sz)//这个函数只能排序整形数据
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
//void qsort(void* base,//指针待排序数组的第一个元素的地址
//         size_t num,//待排序数组元素的个数
//         size_t size,//待排序数组中一个元素的大小
//  int(*cmp)(const void* e1, const void* e2)//函数指针名-cmp指向了一个函数,这个函数是用来比较两个元素大小 
//                                           );
//            e1和e2中存放的是需要比较的两元素的地址
//将比较方法的函数抽离出来,最后根据需求结合
//1.排序整型数组,两个整形可以直接使用>比较
//2.排序结构体数组,两个结构体的数据可能不能直接使用>比较
//也就是不同类型的数据,比较大小,方法是有差距的
//测试qsort排序整形数据
void test1()
{
  int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  print_arr(arr, sz);
  qsort(arr, sz, sizeof(arr[0]), cmp_int);
  print_arr(arr, sz);
}
//测试qsort排序结构体数据
//结构体
//1.按照年龄比较
//2.按照名字比较
struct Stu
{
  char name[20];
  int age;
};
//1.按照年龄比较
int cmp_stu_by_age(const void* e1, const void* e2)
{
  return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name);
}
void test2()
{
  struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
void test3()
{
  struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
  test1();
  //test2();
  //test3();
  return 0;
}

*****************************\n***************************//

void* 类型的指针,他不能进行解引用操作,也不能进行+-整数的操作

void* 类型的指针可以用来存放任意类型数据的地址

void* 是无具体类型的指针,用来接收任意类型的地址

int main()
{
  char c = 'w';
  char* pc = &c;
  int* p = &c;
  void* pv = &c;
  int a = 100;
  pv = &a;
  return 0;
}

........


目录
相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
40 1
|
1月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
49 0
|
1月前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
18 2
|
1月前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
1月前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
1月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
C语言
C语言指针(3)
C语言指针(3)
14 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
15 1