指针初级
计算下列程序输出结果: 上图中pulPtr指向数组第一个元素的地址,解引用为6。 pulPtr+3后指向9的地址,解引用后为9,+=3得到12。
二级指针用来存放一级指针的地址,大小是4字节(32位系统)或8字节(64位系统)。
整型指针+1向后偏移一个整型大小(4字节)。 指针-指针得到的是元素个数。 指针是内存地址,是一串32位整数,可以比较大小。
指针数组:int* arr[5],指针数组创建时候需要指定大小。 数组指针:int (*arr) [5]
写一个函数逆序字符串:
#include <string.h>
#include <assert.h>
void reverse(char* str)
{
assert(str);
char* left = str;
char* right = str + strlen(str) - 1;
char tmp = 0;
while (left < right)
{
tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
int main()
{
char arr[1000] = {0};
scanf("%s", &arr);
reverse(arr);
printf("%s ", arr);
return 0;
}
#include <string.h>
void reverse(char arr[])
{
char tmp = 0;
int i = 0;
int j = strlen(arr) - 1;
while (i < j)
{
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
}
int main()
{
char arr[1000] = {0};
scanf("%s", &arr);
reverse(arr);
printf("%s ", arr);
return 0;
}
::: tip scanf读取内容时遇到空格停止。 gets()读取一行内容,包括空格。 :::
代码思路:当第一项的前一项为0时,每一项的值都是前一项×10+第一项。
int main()
{
int Sn = 0;
int a = 0;
int n = 0;
scanf("%d %d", &a, &n);
int ret = 0;
int i = 0;
for (i = 0;i < n;i++)
{
ret = ret * 10 + a;
Sn += ret;
}
printf("%d", Sn);
}
水仙花数是指一个3位数,它的每个位上的数字的3次幂之和等于它本身。例如:1^3 + 5^3 + 3^3 = 153。
#include <math.h>
int main()
{
int i = 0;
for (i = 0; i < 100000; i++)
{
//判断i是否为水仙花数
//1.计算i的位数 - n位数
int n = 1;
int tmp = i;
int sum = 0;
while (tmp /= 10)
//假设i=125,tmp=125,计算位数时,每次除10去掉一位,位数n+1,
//当结果不为0时(tmp大于1位),继续除10,直到除10结果为0,跳出循环
{
n++;
}
//2.计算i的每一位的n次方之和sum
tmp = i;
while (tmp) //所有位数计算完毕后tmp为0
{
sum += pow(tmp % 10, n); //pow函数可以求次方
tmp /= 10; //求出最后一位数字的次方后去掉该位
}
//3.比较i==sum
if (i == sum) //是水仙花数
{
printf("%d ", i);
}
}
return 0;
}
0 1 2 3 4 5 6 7 8 9 153 370 371 407 1634 8208 9474 54748 92727 93084
上述程序执行后会提示警告,原因是pow返回的类型是double,而sum类型为int。
这条件谁想得到...:
int main()
{
int line = 0;
scanf("%d", &line);
//打印上半部分
int i = 0;
for (i = 0; i < line; i++)
{
//打印空格
int j = 0;
for (j = 0; j < line - 1 - i; j++)
{
printf(" ");
}
//打印*
for (j = 0; j < 2 * i + 1; j++)
{
printf("*");
}
printf("\n");
}
//打印下半部分
for (i = 0; i < line - 1; i++)
{
int j = 0;
//打印空格
for (j = 0; j <= i; j++)
{
printf(" ");
}
//打印*
for (j = 0; j < 2 * (line - 1 - i) - 1; j++)
{
printf("*");
}
printf("\n");
}
return 0;
}
7
*
***
*****
*******
*********
***********
*************
***********
*********
*******
*****
***
*
指针进阶
A
::: tip 旋转一个字符的步骤: 1.把第一个字符的拿出临时存放在一个空间中 2.其余字符依次向前移动 3.把第一个字符放到字符串最后一个位置
需要旋转k个字符,就把上述步骤执行k次。 :::
- 暴力求解:
#include <string.h> void left_move(char* arr, int k) //指针形式 { assert(arr); //assert后的括号内是一个表达式,也可以写成assert(arr != NULL) int len = strlen(arr); int i = 0; for (i = 0; i < k; i++) { char tmp = *arr; int j = 0; for (j = 0; j < len - 1; j++) { *(arr + j) = *(arr + j + 1); } *(arr + len - 1) = tmp; } } int main() { char arr[] = "abcdef"; left_move(arr, 2); //字符串arr左旋两个字符 printf("%s\n", arr); return 0; }
::: tip 只要传参时候写成指针形式,在函数内部就必然使用解引用操作;只要涉及解引用操作,就会涉及指针有效性的问题,需要加上assert断言arr是否有效,如果arr是空指针NULL,程序就会报错(为0报错,非0不报错)。 :::
#include <string.h>
#include <assert.h>
void left_move(char arr[], int k) //数组形式
{
int i = 0;
int tmp = 0;
int len = strlen(arr);
for (i = 0; i < k; i++)
{
tmp = arr[0];
int j = 0;
for (j = 0; j < len - 1; j++)
{
arr[j] = arr[j + 1];
}
arr[len - 1] = tmp;
}
}
int main()
{
char arr[] = "abcdef";
left_move(arr, 2); //字符串arr左旋两个字符
printf("%s\n", arr);
return 0;
}
- 三步翻转:
::: tip
代码思路如下:
也可以先逆序整体,再分段逆序。 :::abcdef ba fedc //分段后两段分别逆序 cdefab //再整体逆序
#include <string.h> void left_move(char arr[],int k) { int left = 0; char tmp = 0; int i = 0; int len = strlen(arr); //分段逆序1 for (left = 0; left < k; left++) { tmp = arr[left]; arr[left] = arr[k - 1]; arr[k - 1] = tmp; } //分段逆序2 for (k; k < len - 1 - i; k++) { tmp = arr[k]; arr[k] = arr[len - 1 - i ]; arr[len - 1 - i ] = tmp; i++; } i = 0; //整体逆序 for (left = 0; left < len - 1 - i; left++) { tmp = arr[left]; arr[left] = arr[len - 1 - i]; arr[len - 1 - i] = tmp; i++; } } int main() { char arr[] = "abcdef"; left_move(arr,2); //2为要旋转的字符数 printf("%s\n", arr); return 0; }
上述代码可以优化为在left_move中放一个reverse函数逆序:cdefab
#include <string.h> #include <assert.h> void reverse(char* left, char* right) { assert(left); assert(right); char tmp = 0; while (left < right) { tmp = *left; *left = *right; *right = tmp; left++; right--; } } void left_move(char* arr, int k) { assert(arr); int len = strlen(arr); assert(k <= len); //确保要旋转的字符数量不超过字符串长度 reverse(arr, arr + k - 1); //逆序左边 reverse(arr + k, arr + len - 1); //逆序右边 reverse(arr, arr + len - 1); //逆序整体 } int main() { char arr[] = "abcdef"; left_move(arr, 2); //2为要旋转的字符数 printf("%s\n", arr); return 0; }
cdefab
#include <string.h>
#include <assert.h>
void reverse(char* left, char* right)
{
assert(left);
assert(right);
char tmp = 0;
while (left < right)
{
tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
void left_move(char* arr, int k)
{
assert(arr);
int len = strlen(arr);
assert(k <= len);
reverse(arr, arr + k - 1); //逆序左边
reverse(arr + k, arr + len - 1); //逆序右边
reverse(arr, arr + len - 1); //逆序整体
}
int is_left_move(char* s1, char* s2)
{
int len = strlen(s1);
int i = 0;
//让s1每次旋转一个字符,然后和s2进行比较,直到把s1旋转后可能有的组合情况全部比较完
for (i = 0; i < len; i++)
{
left_move(s1, 1); //旋转过程中s1在不断变化,+1可以使s1每次多旋转一个字符,不能+i
int ret = strcmp(s1, s2);
if (ret == 0)
{
return 1; //s1旋转可以得到s2,返回1
}
}
return 0;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "cdefab";
int ret = is_left_move(arr1, arr2);
if (ret == 1)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
return 0;
}
Yes
代码优化: 代码思路:如果在原字符串末尾追加一个该字符串本身,组成的新字符串包含原字符串旋转得来的所有可能组合。
字符串库函数
字符串追加的库函数是strcat和strncat。 如上,strncat中的count参数指定了追加的字符个数。 在字符串追加的过程中,以“\0”作为追加结束的标志。 ::: warning strcat不能追加自己,原因是在字符串追加的过程中,字符一个一个追加到原字符串的后面,原字符串的“\0”被自己的第一个字符覆盖,在后续的追加过程中,没有“\0”被追加到字符串后面,追加不能停止,程序崩溃。 ::: 在一个字符串中寻找另一个字符串的函数是strstr。 strstr(str1,str2)表示在str1指向的字符串中寻找str2指向的字符串,如果能找到,返回子串的首字符地址,如果找不到,返回空指针。返回类型是字符指针类型。
#include <string.h>
#include <assert.h>
int is_left_move(char* str1, char* str2)
{
assert(str1);
assert(str2);
int len1 = strlen(str1);
int len2 = strlen(str2);
if (len1 != len2)
{
//两个字符串不相等时,一定不是旋转的得来的
return 0;
}
//在str1的后面追加一个str1
strncat(str1, str1, 6);
//判断str2指向的字符串是不是str1指向的字符串的字串
char* ret = strstr(str1, str2);
if (ret == NULL)
{
return 0;
}
else
{
return 1;
}
}
int main()
{
char arr1[15] = "abcdef";
char arr2[] = "cdefab";
int ret = is_left_move(arr1, arr2);
if(ret == 1)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
return 0;
}
::: tip 时间复杂度O(N): 如果数组有N个元素,从前向后遍历,找一个元素是否存在,最坏的情况是查找n次,此时时间复杂度为0(N)。 ::: 题目要求时间复杂度小于O(N),即不能使用遍历的方法。 代码思路:在数字矩阵中,由于矩阵右上角数字是同行最大同列最小,当判断出矩阵右上角的数字和要查找的数字的大小关系时,可以排除一行或一列,查找效率高。如下图 同样使用矩阵左下角的数字进行比较效果和右上角数字相同。
int FindNum(int arr[4][4], int k, int row, int col)
{
int x = 0;
int y = col - 1;
while (x < row && y >= 0)
{
if (arr[x][y] > k)
{
y -= 1;
col--;
}
else if (arr[x][y] < k)
{
x += 1;
}
else
{
printf("位置是arr[%d][%d]\n", x, y);
return 1;
}
}
return 0;
}
int main()
{
int arr[4][4] = { {1,2,3,4} ,{5,6,7,8},{9,10,11,12},{13,14,15,16} };
int k = 0;
scanf("%d", &k);
int ret = FindNum(arr,k,4,4);
if (ret == 0)
{
printf("没有找到\n");
}
else
{
printf("找到了\n", ret);
}
return 0;
}
8
位置是arr[1][3]
找到了
上述代码中为了保持代码段的整洁,自定义函数内不设置输出语句。 代码优化:
int FindNum(int arr[4][4], int k, int* px, int* py)
{
int x = 0;
int y = *py - 1;
while (x < *px && y >= 0)
{
if (arr[x][y] > k)
{
y -= 1;
(*py)--;
}
else if (arr[x][y] < k)
{
x += 1;
}
else
{
*px = x;
*py = y;
return 1;
}
}
return 0;
}
int main()
{
int arr[4][4] = { {1,2,3,4} ,{5,6,7,8},{9,10,11,12},{13,14,15,16} };
int k = 0;
int x = 4;
int y = 4;
scanf("%d", &k);
int ret = FindNum(arr, k, &x, &y);
if (ret == 0)
{
printf("没有找到\n");
}
else
{
printf("找到了\n", ret);
printf("位置是arr[%d][%d]\n", x, y);
}
return 0;
}
8
找到了
位置是arr[1][3]
::: tip 上述代码使用传址调用既把主函数中矩阵坐标传进自定义函数中,又把自定义函数中得到的矩阵坐标返回到主函数中,这种参数设计称为返回型参数。 :::