您现在的位置是:首页 >其他 >C进阶习题网站首页其他

C进阶习题

是小刘同学啦 2024-07-01 11:59:58
简介C进阶习题

目录

一、指针进阶

二 动态内存分配

三 文件

四 程序环境和预处理

五 练习题


用精神内耗的态度去搞学习搞事业搞钱,用躺平和摆烂的态度对待人际关系,烦恼能消失一大半。


一、指针进阶

1.1 知识点

(1)指针的数据类型,声明的是指针实际指向内容的数据类型;free释放后,并不会自动置为NULL;野指针指向的是未分配或者已经释放的内存地址

(2)int类型的指针数组,就是一个数组,里面存放的是int*

(3)函数return返回值,不能返回两个(可以通过地址传址),可以一个,可以没有

(4)定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数  int (*(*F)(int, int))(int)

(5)声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int*,(int (*(*p)[10])(int *)  【4 5要求会写】

(6)一个地址是否能放到指针里面,要看指针指向内容的类型是否是该地址指向内容的类型

(7)函数的声明:int Add(int a, int b)  或者是 int Add(int, int);

(8) 做选择题,要注意是单选题还是多选题

(9)int arr[3][5]; int (*arr)[5] 指的是,一个指针指向的是二维数组第一行

1.2 实现一个函数,左旋字符串中的K个字符(旋转字符串)可以类比右旋)

方法一:

#include <stdio.h>
#include <string.h>
//思路:字符串的第一个元素,拿出来放在一个空间里面,再把字符串向前移一位,
//再把新的空间里面的元素放在字符串所在空间的最后一个位置
void left_move(char* str, int k)
{
	int i = 0;
	for (i = 0; i < k; i++)
	{
		//每次一个字符
		char tmp = *str;
		int len = 0;
		len = strlen(str);
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			*(str + j) = *(str + j + 1);
		} 
		*(str + len - 1) = tmp;
	}
}

int main()
{
	char arr[] = "abcdef";//一般用数组存放字符串
	int k = 0;
	scanf("%d", &k);
	left_move(arr, k);
	printf("%s
", arr);
	return 0;
}

方法二:

#include <stdio.h>
#include <string.h>
//思路:三步反转法,首先需要左移的字符进行逆序进行逆序,
//然后字符串不需要进行左移的字符进行逆转,最后整个字符串的元素进行逆序
//逆序函数
void reverse(char* left, char* right)
//学习完指针之后,用指针,不需要用数组下标
{
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}

//左移函数
void left_move(char* str, int k)
{
	int len = strlen(str);
	k = k % len;//这个点要注意,容易忽略
	reverse(str, str + k - 1);
	reverse(str + k, str + len - 1);
	reverse(str, str + len - 1);
}

int main()
{
	char arr[] = "abcdef";//一般用数组存放字符串
	int k = 0;
	scanf("%d", &k);
	left_move(arr, k);
	printf("%s
", arr);
	return 0;
}

1.3 有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在,时间复杂度小于O(N)[说明,不可以遍历整个数组]。(杨氏矩阵)

代码一:(这种写法不是特别的好)

#include <stdio.h>
//思路:因为每一行每一列都是递增的,所以需要查找的数字,
//只需要和每一行的最后一个元素进行比较(不能和第一个元素进行比较)【可以从右上角以及左下角开始】,如果大于
//就可以对下一行进行比较。小于后,找到相应的行,
//然后对该行的元素进行比较,从后向前比较,如果该行遍历完之后还是没有,
//就说明没有这个数字
void find_int_arr(int arr[3][3], int r, int c, int k)
{
	int x = 0;
	int y = c - 1;
	while (x <= r - 1 && y >= 0)
	{
		if (arr[x][y] < k)
		{
			x++;
		}
		else if (arr[x][y] > k)
		{
			y--;
		}
		else
		{
			printf("找到了,下标是:x = %d y = %d
", x, y);
			return;
		}
	}
	printf("找不到
");
}


int main()
{
	int arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int k = 0;
	scanf("%d", &k);
	find_int_arr(arr, 3, 3, k);
	return 0;
}

代码2:(优化后的代码)

#include <stdio.h>
//区别:函数返回下标的值,以及找不到的内容,
//通过地址传址
void find_int_arr(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;
		}
	}
	*px = -1;
	*py = -1;
}

int main()
{
	int arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int x = 3;
	int y = 3;
	int k = 0;
	scanf("%d", &k);
	//参数是返回型参数
	find_int_arr(arr, &x, &y, k);
	if (x == -1 && y == -1)
	{
		printf("找不到
");
	}
	else
	{
		printf("找到了,下标是:%d %d", x, y);
	}
	return 0;
}

1.4  写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。是的话,返回1,否返回0

方法一:

//判断是一个字符串的旋转字符
//思路:旋转判断旋转判断
#include <stdio.h>
#include <string.h>
void reverse(char* left, char* right)
{
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
//左移函数
void left_move(char* str, int k)
{
	int len = strlen(str);
	k = k % len;//这个点要注意,容易忽略
	reverse(str, str + k - 1);
	reverse(str + k, str + len - 1);
	reverse(str, str + len - 1);
}
int is_left_move(char* arr1, char* arr2)
{
	int len = strlen(arr1);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		left_move(arr1, 1);
		if (strcmp(arr1, arr2) == 0)
			return 1;
	}
	return 0;
}

int main()
{
	char arr1[] = "AABCD";
	char arr2[] = "BCDAA";
	int ret = is_left_move(arr1, arr2);
	printf("%d
", ret);
	return 0;
}

方法二:

// 判断是一个字符串的旋转字符
//思路:字符串追加本身,再判断是否包含里面,包含就是旋转字符
#include <stdio.h>
#include <string.h>

int is_left_move(char* arr1, char* arr2)
{
	int len1 = strlen(arr1);
	int len2 = strlen(arr2);
	if (len1 != len2)
		return 0;
	strncat(arr1, arr1, len1);
	if (strstr(arr1, arr2))
		return 1;
	else
		return 0; 
}

int main()
{
	char arr1[20] = "AABCD";
	char arr2[] = "BCDAA";
	int ret = is_left_move(arr1, arr2);
	printf("%d
", ret);
	return 0;
}

二 动态内存分配

2.1 知识点

(1)枚举,打印出来,依次加一,相较于上一个(除了,定义时候就赋值的)

(2)define定义的标识符常量,是替换内容。需要带进去,而不是计算完再代入(易错点)

2.2代码结果

int main()
{
  unsigned char puc[4];
  struct tagPIM
  {
    unsigned char ucPim1;
    unsigned char ucData0 : 1;
    unsigned char ucData1 : 2;
    unsigned char ucData2 : 3;
  }*pstPimData;
  pstPimData = (struct tagPIM*)puc;
  memset(puc,0,4);
  pstPimData->ucPim1 = 2; 
  pstPimData->ucData0 = 3;
  pstPimData->ucData1 = 4;
  pstPimData->ucData2 = 5;
  printf("%02x %02x %02x %02x
",puc[0], puc[1], puc[2], puc[3]);
  return 0;
}

结果为:02 29 00 00

知识点:%x指的是打印16进制;%02x指的是,打印两位

2.3  

#include<stdio.h>
int main()
{
  union
  {
    short k;
    char i[2];
  }*s, a;
  s = &a;
  s->i[0] = 0x39;
  s->i[1] = 0x38;
  printf("%x
", a.k);
  return 0;
}

打印结果:3839

2.4 BC100-有序序列合并

 输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。输入描述:输入包含三行,第一行包含两个正整数n, m,用空格分隔。n表示第二行第一个升序序列中数字的个数,m表示第三行第二个升序序列中数字的个数。第二行包含n个整数,用空格分隔。第三行包含m个整数,用空格分隔。输出描述:输出为一行,输出长度为n+m的升序序列,即长度为n的升序序列和长度为m的升序序列中的元素重新进行升序序列排列合并。

#include <stdio.h>
int main()
{
	int n = 0;
	int m = 0;
    //输入  
	scanf("%d %d", &n, &m);
	int arr1[n];//在C99标准就可以,牛客网支持
	int arr2[m];
    //输入两个升序的序列
    int i = 0;
    for (i = 0; i < n; i++)
    {
        scanf("%d ", &arr1[i]);
    }
    for (i = 0; i < m; i++)
    {
        scanf("%d ", &arr2[i]);
    }
    //输出序列
    int j = 0;
    i = 0;
    while ((i < n) && (j < m))
    {
        if (arr1[i] < arr2[j])
        {
            printf("%d ", arr1[i]);
            i++;
        }
        else
        {
            printf("%d ", arr2[j]);
            j++;
        }
    }
    if (i == n)
    {
        for (; j < m; j++)
        {
            printf("%d ", arr2[j]);
        }
    }
    else 
    {
        for (; i < n; i++)
        {
            printf("%d ", arr1[i]);
        }
    }

	return 0;
}

2.5 BC38 变种水仙花

描述:变种水仙花数 - Lily Number:把任意的数字,从中间拆分成两个数字,比如1461 可以拆分成(1和461),(14和61),(146和1),如果所有拆分后的乘积之和等于自身,则是一个Lily Number。例如:655 = 6 * 55 + 65 * 5;1461 = 1*461 + 14*61 + 146*1求出 5位数中的所有 Lily Number。输入描述:无 输出描述:一行,5位数中的所有 Lily Number,每两个数之间间隔一个空格。

#include <stdio.h>
#include <math.h>
int main()
{
	// 12345 12345/10 * 12345%10
	//12345/100 * 12345%100

	int i = 0;
	for (i = 10000; i < 99999; i++)
	{
		int j = 0;
		int sum = 0;
		for (j = 1; j <= 4; j++)
		{
			int m = i / pow(10, j);
			int n = i % (int)pow(10, j);
			sum = sum + m * n;
		}
		if (i == sum)
		{
			printf("%d ", i);
		}
	}
	return 0;
}

知识点:(1)%的两边的数字,必须是整形(2)pow返回的值为double类型的

2.6一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。编写一个函数找出这两个只出现一次的数字。例如:有数组的元素是:1,2,3,4,5,1,2,3,4,6;只有5和6只出现1次,要找出5和6.

#include <stdio.h>
//进行分组:只出现一次的两个数字分到两个分组中,一组一个
// 如何进行分组:没有进行分组的时候,组内数字进行异或出现的结果,结果是1的位置说明,两个数字是不同的,按照这个位置的数字是1还是0,就可以将这两个数字分到不同的组中
//每个组都满足,只有一个数字出现一次,其他数字都是成对出现的,这样每一个组的数字进行异或,那么结果就是只出现一次的那个数字
int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 6 };
	//0异或任何数还是等于任何数
	//1.先数组内进行异或
	int ret = 0;
	int sz = sizeof(arr) / sizeof(int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		ret = ret ^ arr[i];
	}
	//计算ret的二进制中第几位是1
	int pos = 0;
	for (i = 0; i < 32; i++)
	{
		if (((ret >> 1) & 1) == 1)
		{
			pos = i;
			break;
		}
	}
	//3.按照pos位的0或者1进行分组
	int m = 0;
	int n = 0;
	for (i = 0; i < sz; i++)
	{
		if (((arr[i] >> pos) & 1) == 1)
		{
			m = m ^ arr[i];
		}
		else
		{
			n = n ^ arr[i];
		}
	}
	printf("%d %d
", m, n);
	return 0;
}

2.7 模拟实现atoi

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
//atoi   a   to  i  头文件是<stdlib.h>
//把字符串转换为整形
//'1' -'0' = 1 字符1减去字符0等于1;'2' - '0' = 2
//比如,字符串"12" 0*10+1=1; 1*10+2=12;
//比如“123”  0*10+1=1; 1*10+2=12 12*10+3=123
//空指针 空字符串 正负 非数字字符  超大数字(超过int整形范围的数字)空白字符
enum State   //枚举
{
	INVALID,//valid 合法的
	VALID
};
enum State status = INVALID;
//正常的情况只有一种,非法的情况有许多,所以这里直接写INVALID
int my_atoi(const char* str)
{
	assert(str);
	//空字符串的问题
	if (*str == '')
		return 0;//这里的0,就是非法0,的0
	//空白字符,跳过空白字符,只打印空白字符后面的数字字符串即可
	while (isspace(*str))//isspace可以判断是否为空白字符,是的话返回真,头文件为ctype.h
	{
		str++;
	}

	//+-
	int flag = 1;//如果没有正负号的话,就是正数,所以在这里默认是1
	if (*str == '+')
	{
		flag = 1;
		str++;
	}
	else if (*str == '-')
	{
		flag = -1;
		str++;
	}
	//遍历字符串,判断是否有非数字字符
	long long n = 0;//打印的shuzi//可以判断出超大字符
	while (isdigit(*str))
	{
		n = (n * 10 )+ flag*(*str - '0');
		if (n > INT_MAX || n < INT_MIN)//INT_MAX和INT_MIN分别是整形的最大值和最小值
		{
			return 0;
		}
		str++;
	}
	if (*str == '')
	{
		//合法返回
		status = VALID;
		return (int)n;
	}
	return (int)n;
}


int main()
{
	int ret = my_atoi("   123a");
	if (status == VALID)
		printf("%d
", ret);
	else
		printf("非法返回
");
	return 0;
}

三 文件

3.1 知识点

(1)文件名中有一些禁止使用的字符

(2)getchar也是适用于标准输入流的字符输入函数(即从键盘中获取)

(3)return 0;结束所在函数的程序,exit(0);结束整个程序

(4)scanf和printf是针对标准输入、输出流的格式化输入、输出语句。

(5)文件名可以不包含后缀名。

(6)文件的后缀名决定了一个文件的默认打开方式

四 程序环境和预处理

4.1 知识点

(1)链接可以发现被调用的函数未定义(链接的时候,多个目标文件进行链接的时候会通过符号表,查看来自外部的符号是否真实存在)

(2)编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。

链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。

(3)避免在头文件中定义变量,引用文件的时候,会导致变量的重复定义

4.2 写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。

#include <stdio.h>
//1011 奇数位拿出来,偶数位置0  0001(A) & 01
//     偶数位拿出来,奇数位置0, 1010 (B)  &10
// 1011=0001+1010,
//因为奇数位和偶数位互换,则A向左移动一位0010,B向右移动一位0101,在进行相加即可0111
#define SWAP(x) (((x&0x55555555)<<1) + (x&0xaaaaaaaa)>>1)


int main()
{
	int a = 10;
	printf("%d
", SWAP(a));
	return 0;
}

4.3 写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明。考察:offsetof宏的实现

#include <stdio.h>
#define OFFSETOF(s_name, m_name)    (int)&(((s_name*)0)->m_name)
// (s_name*)0假设结构体的地址为0,  s->a 找到的是假设的成员,& 此时假设成员的地址就是偏移量
struct S
{
	char a;
	int b;
	double c;
};

int main()
{
	struct S s = { 0 };
	printf("%d
", OFFSETOF(struct S, a));
	printf("%d
", OFFSETOF(struct S, b));
	printf("%d
", OFFSETOF(struct S, c));
	return 0;
}

(1)第一个位置的偏移量为0(单位字节)

(2)offsetof(结构体类型,结构体成员名)

(3)offsetof的头文件是<stddef.h>

五 练习题

5.1 知识点

(1)unsigned 的数值大于等于0;j = 0;unsigned char i = 7;for (; i > 0; i-=3){++j};那么这个for循环,只有值等于0的时候,才可以停止。j = 173;173次

(2)unsigned char a = 10;a++;首先10是一个整形,它是32位,但是由于放到char类型中,所以就发生截断,0000 1010;a++,因为是表达式计算,所以就会发生整形提升(所以只有计算的时候,才会注意到是unsigned还是signed ),经过计算后,还是一个32位,但是由于a是char类型的,所以又发生截断。就是0000 1011。(unsigned整形提升的时候用,char 截断的时候需要)

(3)C++ const修饰的变量彻底成为常量,C语言中是常变量

(4)野指针是指未分配或者已经释放的内存地址。

5.2 代码运行结果,输入1abcedf2df<回车>输出结果

#include<stdio.h>
int main()
{
	char a = 0, ch;
	while ((ch = getchar()) != '
')
	{
		if (a % 2 != 0 && (ch >= 'a' && ch <= 'z'))
			ch = ch - 'a' + 'A';
		a++;
		putchar(ch);
	}
	printf("
");
}

知识点:(1)‘a’的ASCII值是97,'A'的ASCII值是65(2)char小写字母减去32,就是小写字母对应的大写字母。(-‘a’+'A'为-32)

运行结果:1AbCeDf2dF

5.3 Fibonacci数列是这样定义的:F(0) = 0;F(1) = 1;for each i>=2 F[i] = F[i-1] + F[i - 2],因此,Fibnonacci数列就形如:0,1,1,2,3,5,8,13,…在Fibonacci数列中的数我们称之为Fibnoacci数。给你一个N,你想让其成为一个变成一个Fibnacci数,每一步你可以把当前数字变为X-1或者X+1,现在给你一个数N,求最少需要多少步可以变为Fibonacci数

#include <stdio.h>

int main()
{
	int a = 0;
	int b = 1;
	int c = 0;
	c = a + b;
	int n = 0;
	int d = 0;
	scanf("%d", &n);
	while (1)
	{
		if (n == b)
		{
			d = 0;
			break;
		}
		else if (n < b)
		{
			d = ((n - a) > (b - n) ? (b - n) : (n - a));
			break;
		}
		else
		{
			a = b;
			b = c;
			c = a + b;
		}

	}
	printf("%d
", d);

	return 0;
}

abs(a,b)求两个数的绝对值

5.4 请实现一个函数,将字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换后的字符串为We%20Are%20Happy。

class Solution {
public:
	void replaceSpace(char *str,int length) {
    char* a = str;
	int space = 0;
	while (*a)
	{
		if (*a == ' ')
		{
			space++;
		}
		a++;
	}
	int newlength = length + (2 * space);
	int end1 = length - 1;
	int end2 = newlength - 1;
	while (end1 != end2)
	{
		if (str[end1] != ' ')
		{
			str[end2--] = str[end1--];
		}
		else
		{
			str[end2--] = '0';
			str[end2--] = '2';
			str[end2--] = '%';
			end1--;
		}
	}
	}
};
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。