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