【C语言】指针内容全篇(下)

简介: 【C语言】指针内容全篇(下)

演示qsort函数的使用


   qsort函数是快速排序函数,它是一个库函数。学习qsort函数的使用前,我们先回顾一下冒泡排序。

1e7d05ebad4245438179d761300ff03a.jpg

//冒泡排序
#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
  int i = 0;
  int j = 0;
  //冒泡排序的趟数
  for (i = 0; i < sz - 1; i++)
  {
    //一趟冒泡排序
    for (j = 0; j < sz - 1 - i; j++)
    {
      int temp = 0;
      if (arr[j] > arr[j + 1])
      {
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
}
void print_arr(int arr[], int sz)
{
  int i;
  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]);
  printf("排序前\n");
  print_arr(arr, sz);
  bubble_sort(arr, sz);
  printf("排序后\n");
  print_arr(arr, sz);
  return 0;
}


   那冒泡排序跟我们要学的qsort函数有什么关系吗?其实qsort函数就能帮助我们快速实现升序排序,而且还能对字符串数组和结构体数据进行排序。但是在这里,我们只需要知道怎么使用qsort函数就行了,并不需要它是怎么来的。


前置知识


介绍qsort函数前,需要先解释一个东西 - void* 。平常,我们定义一个 XX 类型的变量,就必须使用 XX 类型的指针来指向变量的地址。而void* 类型,就可以很好地接收各种类型变量的地址并存储起来。不恰当的比喻:void* 就像一个 “垃圾桶” ,什么都可以装。


   注意事项:1.void*类型 —— ⽆类型,不能进⾏解引⽤操作。因为不能确定访问⼏个字节

(指针的类型确定访问的字节⼤⼩)  2.void*类型,不能进⾏+、-整数的操作,因为不能确定⼀步⾛⼏个字节(指针的类型确定步⻓)只有将void*类型的指针强制类型转换才能进行解引用操作和+-整数。

6f2248383cc1456e94df03735a333698.png43f4962759304097aa8d72bd38f455ec.png


   使用qsort函数排序整型数组(升序)


#include <stdio.h>
#include <stdlib.h>
//使用qsort函数需要包含头文件stdlib.h
void print_arr(int arr[], int sz)
{
  int i;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
    //因为待排序的数据是整型,所以将e1和e2强制类型转换为整型指针
    //只有这样才能进行解引用操作和+-整数
}
int main()
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  //排序
  qsort(arr, sz, sizeof(arr[0]), cmp_int);
  //打印
  print_arr(arr, sz);
  return 0;
}

a8afbee41f744f7ab1ae4e2111cbe8ed.png


  使用qsort函数排序结构体数据(年龄)


#include <stdio.h>
#include <stdlib.h>
struct Stu
{
  char name[20];
  int age;
};
//定义结构体
int cmp_s_age(const void* e1, const void* e2)
{
  return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
//将e1和e2强制类型转换为struct Stu*
int main()
{
  //使用qsort函数排序结构体数据
  struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
  int sz = sizeof(s) / sizeof(s[0]);
  //按照年龄来排序
  qsort(s, sz, sizeof(s[0]), cmp_s_age);
  for (int i = 0; i < sz; i++)
  {
    printf("%s:%d\n", s[i].name, s[i].age);
        //打印数据
  }
  return 0;
}

cbb74dda0b484aca93ba5e05f8cb204b.png

使用qsort函数排序结构体数据(名字)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
  char name[20];
  int age;
};
int cmp_s_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    //strcmp函数可以比较字符串的大小,使用它需要引头文件string.h
}
int main()
{
    //使用qsort函数排序结构体数据
  struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
  int sz = sizeof(s) / sizeof(s[0]);
  //按照名字来排序
  qsort(s, sz, sizeof(s[0]), cmp_s_name);
  for (int i = 0; i < sz; i++)
  {
    printf("%s:%d\n", s[i].name, s[i].age);
  }
  return 0;
}

31fc1956bd3c415eb177610bb18cbbf3.png


  注意:如果想要使用qsort函数实现降序排序的话,可以在return后面多加一个负号也可以将e1和e2的位置对调


   其实,qsort函就也是一个回调函数的应用。


10.自定义bubble_qsort函数


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
  char name[20];
  int age;
};
int cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
int cmp_by_age(const void* e1, const void* e2)
{
  return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
int cmp_by_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void Swap(char* buf1, char* buf2, int width)
{
  //因为数据在内存是一个字节一个字节存储的,所以我们将两个元素的全部字节
  //交换了,这两个元素就完成交换了
  int i = 0;
  for (i = 0; i < width; i++) //width是一个元素的大小,单位是字节
  {
    //交换一个字节
    char temp = *buf1;
    *buf1 = *buf2;
    *buf2 = temp;
    //字符指针自加,准备交换下一个字节
    buf1++;
    buf2++;
  }
}
//模仿qsort函数实现一个冒泡排序的通用算法
void bubble_qsort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
  int i = 0;
  //趟数
  for (i = 0; i < sz; i++)
  {
    //一趟的排序
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      //两个元素比较
      //类比arr[j]和arr[j+1]
      if (cmp((char*)base+j*width,(char*)base+(j+1)*width) > 0)
      {
        //交换
        Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
      }
    }
  }
}
void test1()
{
  //整型数据的排序
  int arr[10] = { 1,3,5,7,9,0,2,4,6,8 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  //排序
  bubble_qsort(arr, sz, sizeof(arr[0]), cmp_int);
  //打印
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
void test2()
{
  //使用qsort函数排序结构体数据
  struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
  int sz = sizeof(s) / sizeof(s[0]);
  struct Stu* p = s;//p为结构体指针
  //按照年龄来排序
  //bubble_qsort(s, sz, sizeof(s[0]), cmp_by_age);
  //按照名字来排序
  bubble_qsort(s, sz, sizeof(s[0]), cmp_by_name);
  for (int i = 0; i < sz; i++)
  {
    //结构体数据的两种打印方法
    //printf("%s:%d\n", s[i].name, s[i].age);
    printf("%s:%d\n", (p+i)->name, (p+i)->age);
  }
}
int main()
{
  //test1();
  test2();
}


 分析 (这样可以看一下,可以帮助你理解上面的代码)


bea3700c67734690ac5099db0d9c2b84.png


   输出结果

346c23f6d3354bb78a4c8f7a9aa9440d.png

60bcccae68984758b825ba5fbbd5e0a8.png


1b6c32aa156d4ea49f4a93182c90efe9.png

1b6c32aa156d4ea49f4a93182c90efe9.png


11.指针和数组笔试题解析


   为了答案的统一,下面的这些代码都是在32位平台下运行的。因为在不同的平台下运行,指针的大小会出现结果不统一。但如果32位平台的结果和64位平台的结果不一样的话,我会将两个结果都写出来。


在前面,我们已经学过数组名的意义:1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小;2.&数组名,数组名表示整个数组,取出的是整个数组的大小;3.除此之外,所有的数组名都表示数组首元素的地址。这个知识将会帮助我们分析下面的代码。

6313c87c41714a208a9ce37a0c72aa1c.png

7ca4ab4a4bab4fa48b9ec49e4bd34f78.png

e9217e8ade3a4f3b8ff2f6b1fc20fe23.png

20ebaca10d8b4def9c171bb6f1f67eac.png

d3093586e3244c67be0a805a0acfa1a9.png

db7d6aa2fcbe4e988dff3485d059c941.png

697e782469f74c0a8f78bf9205b218f1.pnga478f35c5b324545b98437945f83c636.png


754e385a4d864ffd9b8e110ce50d013b.png


   总结:数组名的意义                                                                                                1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。  

    2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。                                 3.除此之外所以的数组名都表示数组首元素的地址


12.指针笔试题


笔试题1


#include <stdio.h>
int main()
{
  int a[5] = { 1, 2, 3, 4, 5 };
  int* ptr = (int*)(&a + 1);//将数组指针强制类型转换为整型指针
  printf("%d,%d", *(a + 1), *(ptr - 1));
  return 0;
}

2c29f647176f49a0808675c172e13a4c.png

笔试题2


   注意:在32位平台下,结构体的大小为20个字节;在64位平台下,结构体的大小为32个字节。该程序是在32位平台下运行的。


#include <stdio.h>
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
  printf("%p\n", p + 0x1);
  printf("%p\n", (unsigned long)p + 0x1);
  printf("%p\n", (unsigned int*)p + 0x1);
  return 0;
}

90a719b84cff4d5d9457316dc93bd82e.png

笔试题3


#include <stdio.h>
int main()
{
  int a[4] = { 1, 2, 3, 4 };
  int* ptr1 = (int*)(&a + 1);
  int* ptr2 = (int*)((int)a + 1);
  printf("%x,%x", ptr1[-1], *ptr2);
  return 0;
}

2cbd12d0df124347ab7651bdad6e77eb.png


笔试题4


#include <stdio.h>
int main()
{
  int a[3][2] = { (0, 1), (2, 3), (4, 5) };
  int* p;
  p = a[0];
  printf("%d", p[0]); 
  return 0;
}

31d5858b76294c60a17bebe6b818e48d.png

笔试题5


#include <stdio.h>
int main()
{
  int a[5][5];
  int(*p)[4];
  p = a;
  printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
  return 0;
}

e02a742b6b4342a58b01c2f234556a9b.png

笔试题6


#include <stdio.h>
int main()
{
  int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  int* ptr1 = (int*)(&aa + 1);
  int* ptr2 = (int*)(*(aa + 1));
  printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
  return 0;
}

aa5e7598be9d449b83eaef7b3062dd2a.png

笔试题7


#include <stdio.h>
int main()
{
  char* a[] = { "work","at","alibaba" };
  char** pa = a;
  pa++;
  printf("%s\n", *pa);
  return 0;
}

e16c6afbecb34062895f376182ba1d78.png

笔试题8


#include <stdio.h>
int main()
{
  char* c[] = { "ENTER","NEW","POINT","FIRST" };
  char** cp[] = { c + 3,c + 2,c + 1,c };
  char*** cpp = cp;
  printf("%s\n", **++cpp);
  printf("%s\n", *-- * ++cpp + 3);
  printf("%s\n", *cpp[-2] + 3);
  printf("%s\n", cpp[-1][-1] + 1);
  return 0;
}

193d6871369948da877204589a6d5228.png

d2915abadfef474d8f8f8b28ce8eaf1c.png

 

 以上就是C语言指针的全部内容了。如果你能够弄懂上面的知识的话,相信你对C语言的指针已经掌握了差不多了。这篇内容断断续续写了6天,终于写完了,真的没想到自己居然写完了。真的很不容易,希望大家可以点个赞支持一下,谢谢大家了!!!

相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
36 1
|
27天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
26天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
26天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
26天前
|
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)
11 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
13 1