C语言指针——练习

简介: C语言指针——练习

第一题.strlen函数的模拟实现


strlen函数用于计算字符串的长度,以下是一个模拟实现:


int strlen(char *str)
 {
    int len = 0;
    while (*str != '\0')
 {
        len++;
        str++;
 }
    return len;
}

该函数通过一个while循环来遍历字符串,每遍历一个字符,计数器len就加1。当遍历到字符串的结尾字符'\0'时,循环结束,函数返回计数器的值,即字符串的长度。


第二题 模拟实现strcpy


strcpy是C语言标准库中的函数,用于将字符串复制到另一个字符串中。


以下是模拟实现strcpy的代码:


char* strcpy(char* dest, const char* src) {
    char* p_dest = dest;
    while (*src != '\0') {
        *p_dest++ = *src++;
    }
    *p_dest = '\0'; // 添加字符串结尾符
    return dest;
}

strcpy的工作原理是遍历源字符串,逐个字符地复制到目标字符串中,遇到字符串结尾符\0时停止复制并在目标字符串末尾添加一个\0。在模拟实现中,使用指针p_dest指向目标字符串,每复制一个字符,就将指针后移一位;使用指针src指向源字符串,每读取一个字符,就将指针后移一位。最后返回指向目标字符串的指针dest。


第三题.模仿qsort的功能实现一个通用的冒泡排序


void swap(char* buf1, char* buf2, size_t size)
{
  int i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
{
  //冒泡排序的趟数
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    //一趟冒泡排序
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
    
      if(cmp((char*)base + j * size, (char*)base + (j + 1) * size)<0)
      {
        //交换
        swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
      }
    }
  }
}

第四题.实现一个函数,可以左旋字符串中的k个字符。


例如:ABCD左旋一个字符得到BCDA


          ABCD左旋两个字符得到CDAB


设计循环使其可以旋1次,然后让他执行n次是一个最简单的思路:


void leftRound(char * src, int time)
{
  int i, j, tmp;
  int len = strlen(src);
  time %= len; //长度为5的情况下,旋转6、11、16...次相当于1次,7、12、17...次相当于2次,以此类推。
  for (i = 0; i < time; i++) //执行k次的单次平移
  {
    tmp = src[0];
    for(j = 0; j < len - 1; j++) //单次平移
    {
      src[j] = src[j + 1];
    }
    src[j] = tmp;
  }
}

改进一:


这个思路当然可以,但是一次一次转毕竟太麻烦,就不能一次到位么?


当然可以,我们可以选择拼接法,一次到位:


void leftRound(char * src, int time)
{
  int len = strlen(src);
  int pos = time % len; //断开位置的下标
  char tmp[256] = { 0 }; //更准确的话可以选择malloc len + 1个字节的空间来做这个tmp
  
  strcpy(tmp, src + pos); //先将后面的全部拷过来
  strncat(tmp, src, pos); //然后将前面几个接上
  strcpy(src, tmp); //最后拷回去
}

改进二:


这个方法要用到一个数组形成的辅助空间,让人觉得有点不爽,还可以有更好的选择,例如ABCDEFG,左旋3次后变成DEFGABC,有一个特殊的操作方式:


先将要左旋的前三个家伙逆序(CBADEFG),然后将后半段也逆序(CBAGFED),最后整体逆序(DEFGABC)即可。这样只需要做数值交换即可,可以写一个函数帮我们完成局部逆序,代码如下:


void reverse_part(char *str, int start, int end) //将字符串从start到end这一段逆序
{
  int i, j;
  char tmp;
 
  for (i = start, j = end; i < j; i++, j--)
  {
    tmp = str[i];
    str[i] = str[j];
    str[j] = tmp;
  }
}
 
void leftRound(char * src, int time)
{
  int len = strlen(src);
  int pos = time % len;
  reverse_part(src, 0, pos - 1); //逆序前段
  reverse_part(src, pos, len - 1); //逆序后段
  reverse_part(src, 0, len - 1); //整体逆序
}

第五题.写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。


例如:给定s1 =AABCD和s2 = BCDAA,返回1


给定s1=abcd和s2=ACBD,返回0.


AABCD左旋一个字符得到ABCDA


AABCD左旋两个字符得到BCDAA


AABCD右旋一个字符得到DAABC


解析:


本题当然可以将所有旋转后的结果放到一个数组里,然后进行查找,但是这种做法既不好操作,也太费事,但是这题有一个很简单的做法:


其实ABCDE无论怎么旋,旋转后的所有结果,都包含在了ABCDEABCD这个字符串里了。


所以做法很简单,只需要将原字符串再来一遍接在后面,然后找一找待查找的字符串是不是两倍原字符串的子集即可。


int findRound(const char * src, char * find)
{
  char tmp[256] = { 0 }; //用一个辅助空间将原字符串做成两倍原字符串
  strcpy(tmp, src); //先拷贝一遍
  strcat(tmp, src); //再连接一遍
  return strstr(tmp, find) != NULL; //看看找不找得到
}

方法二


int is_left_move(char* str1, char* str2)
{
  int len1 = strlen(str1);
  int len2 = strlen(str2);
  if (len1 != len2)
    return 0;
 
  strncat(str1, str1, len1);
  if (strstr(str1, str2) == NULL)
    return 0;
  else
    return 1;
}
 
int main()
{
  char arr1[20] = "abcdef";//abcdefabcdef
  //bcdefa
  //cdefab
  //defabc
  //efabcd
  //fabcde
  //abcdef
  char arr2[] = "cdef";
 
  int ret = is_left_move(arr1, arr2);//判断arr2是否是arr1旋转所得
 
  if (ret == 1)
    printf("Yes\n");
  else
    printf("No\n");
 
  return 0;
}

第六题.杨氏矩阵


有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。


要求:时间复杂度小于O(N);


#include <stdio.h>
 
int findnum(int a[][3], int x, int y, int f) //第一个参数的类型需要调整
{
  int i = 0, j = y - 1; //从右上角开始遍历
  while (j >= 0 && i < x)
  {
    if (a[i][j] < f) //比我大就向下
    {
      i++;
    }
    else if (a[i][j] > f) //比我小就向左
    {
      j--;
    }
    else
    {
      return 1;
    }
  }
  return 0;
}
 
int main()
{
  int a[][3] = { {1, 3, 5},
          {3, 5, 7}, 
          {5, 7, 9} }; //一个示例
 
  if (findnum(a, 3, 3, 2))
  {
    printf("It has been found!\n");
  }
  else
  {
    printf("It hasn't been found!\n");
  }
 
  return 0;
}

方法二,可返回所求值的坐标


int Find(int arr[3][3], int *px, int *py, int k)
{
  int x = 0;
  int y = *py - 1;
 
  while (x <= *px - 1 && y >= 0)
  {
    if (arr[x][y] < k)
    {
      x++;
    }
    else if (arr[x][y] > k)
    {
      y--;
    }
    else
    {
      *px = x; 
      *py = y;
      return 1;
    }
  }
  *px = -1;
  *py = -1;
  return 0;
}
 
int main()
{
  int arr[3][3] = { 1,2,3, 4,5,6, 7,8,9 };
  int k = 0;
  int x = 3;
  int y = 3;
  scanf("%d", &k);//7
  int ret = Find(arr, &x, &y, k);//存在返回1,如果不存在返回0
  if (ret == 1)
  {
    printf("Yes\n");
    printf("%d %d\n", x, y);
  }
  else
  {
    printf("No\n");
    printf("%d %d\n", x, y);
  }
    
 
  return 0;
}

第七题.下面程序的结果是:( )


int main()

{

 int aa[2][5] = {10,9,8,7,6,5,4,3,2,1};

 int *ptr1 = (int *)(&aa + 1);

 int *ptr2 = (int *)(*(aa + 1));

 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));

 return 0;

}

A.1, 6

B.10, 5

C.10, 1

D.1, 5


&aa的类型是int (*)[2][5],加一操作会导致跳转一个int [2][5]的长度,直接跑到刚好越界的位置。减一以后回到最后一个位置1处。*(aa + 1)相当于aa[1],也就是第二行的首地址,自然是5的位置。减一以后由于多维数组空间的连续性,会回到上一行末尾的6处。故选A。


第八题.模拟实现了二维数组


方法一.静态开辟

 
int main()
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
 
  int* arr[3] = {arr1, arr2, arr3};
 
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      //printf("%d ", *(arr[i] + j));
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

方法二.动态开辟

#include <stdlib.h>
//
int main()
{
  int **arr = (int**)malloc(3*sizeof(int*));
  int i = 0;
  for (i = 0; i < 3; i++)
  {
  *(arr + i) = (int*)malloc(5*sizeof(int));
  }
  //初始化
  for (i = 0; i < 3; i++)
  {
  int j = 0;
  for (j = 0; j < 5; j++)
  {
    arr[i][j] = 1;
  }
  }
  //打印
  for (i = 0; i < 3; i++)
  {
  int j = 0;
  for (j = 0; j < 5; j++)
  {
    printf("%d ", arr[i][j]);
  }
  printf("\n");
  }
  //释放
  for (i = 0; i < 3; i++)
  {
  free(arr[i]);
  }
  free(arr);
  arr = NULL;
  return 0;
}

第六题.


/int main()
//{
//  int a[5] = { 1, 2, 3, 4, 5 };
//  int* ptr = (int*)(&a + 1);
//  printf("%d, %d", *(a + 1), *(ptr - 1));
//
//  return 0;
//}

image.png

第七题


//由于还没学习结构体,这里告知结构体的大小是20个字节
//X86 环境下演示
struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
} * p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
//0x开头的数字是16进制的数字
int main()
{
  p = (struct Test*)0x100000;
  printf("%p\n", p + 0x1);
  printf("%p\n", (unsigned long)p + 0x1);
  printf("%p\n", (unsigned int*)p + 0x1);
 
  return 0;
}

image.png

image.png

第八题


#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;
}
 

int a[3][2] = { (0, 1), (2, 3), (4, 5) };//实际初始化为1 3 5,为逗号表达式取两者中的最大值

image.png

第九题



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;
}

image.png

第十题


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;
}
 

image.png

第十一题


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

  image.png

第十二题


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;
}
相关文章
|
23天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
76 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
23天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
47 9
|
23天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
41 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
117 13
|
26天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
26天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
93 3
|
27天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
26天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
36 1
|
1月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
1月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。