3.11知识点3
- [1. break 语句和 continue 语句有什么区别?](#1. break 语句和 continue 语句有什么区别?)
- [2. 使用 goto 计算 1-2+3-4+......+99-100 的值。](#2. 使用 goto 计算 1-2+3-4+......+99-100 的值。)
- [3. 输入一个整数,判断这个数是不是素数。](#3. 输入一个整数,判断这个数是不是素数。)
- [4. 写程序生成前 100 个素数。](#4. 写程序生成前 100 个素数。)
- 5
- [6. (a) 对于一个大小为N的数组,它的下标范围是多少?](#6. (a) 对于一个大小为N的数组,它的下标范围是多少?)
- [7. 如何计算`arr[i]`的地址?](#7. 如何计算
arr[i]
的地址?) - [8. 为什么程序员会有这样一个刻板印象:数组的效率比链表高?](#8. 为什么程序员会有这样一个刻板印象:数组的效率比链表高?)
- [9. 写一个能计算数组大小的宏函数。](#9. 写一个能计算数组大小的宏函数。)
- [10. 写一个随机发牌的小程序:用户指定发几张牌,程序打印手牌。](#10. 写一个随机发牌的小程序:用户指定发几张牌,程序打印手牌。)
- [11. 为什么传递数组时,除了要传入数组名,还要传入长度?](#11. 为什么传递数组时,除了要传入数组名,还要传入长度?)
- [12. 二维数组`arr[M][N]`的本质是一个一维数组,这个一维数组有多少个元素,每个元素的类型是什么?`arr[i][j]`的地址是多少?为什么二维数组传递的时候,不能省略第二个维度?](#12. 二维数组
arr[M][N]
的本质是一个一维数组,这个一维数组有多少个元素,每个元素的类型是什么?arr[i][j]
的地址是多少?为什么二维数组传递的时候,不能省略第二个维度?) - [13. 什么是局部变量,什么是外部变量,它们的作用域分别是什么?](#13. 什么是局部变量,什么是外部变量,它们的作用域分别是什么?)
- [14. 存储期限有哪些?局部变量默认的存储期限是什么?怎么改变局部变量的存储期限?](#14. 存储期限有哪些?局部变量默认的存储期限是什么?怎么改变局部变量的存储期限?)
- [15. 德州扑克:写一个程序循环读取 5 张手牌 (输入 0 结束程序),然后把手中的牌分为下面某一类:1.同花顺 2.四张 3.葫芦 (3 + 2) 4. 同花 5. 顺子 6.三张 7.两对 8. 一对 9.高牌。程序对话如下:](#15. 德州扑克:写一个程序循环读取 5 张手牌 (输入 0 结束程序),然后把手中的牌分为下面某一类:1.同花顺 2.四张 3.葫芦 (3 + 2) 4. 同花 5. 顺子 6.三张 7.两对 8. 一对 9.高牌。程序对话如下:)
- [16. 约瑟夫环:n 个人站成一圈,每个 m 个人处决一个人。假设这 n 个人的编号为 1, 2, ..., n,并且从 1 开始数,问最终活下来的人编号是多少?](#16. 约瑟夫环:n 个人站成一圈,每个 m 个人处决一个人。假设这 n 个人的编号为 1, 2, ..., n,并且从 1 开始数,问最终活下来的人编号是多少?)
- [17. 约瑟夫排列:我们可以在此基础上定义约瑟夫排列,编号为 1, 2, ..., n 的 n 个人围成一个圈,每 m 个人移出一个人,直到移出所有的人。移出的顺序我们称为 (n, m)-约瑟夫排列。比如 (7, 3)-约瑟夫排列为 < 3, 6, 2, 7, 5, 1, 4>。请写一个程序,打印 (n, m)-约瑟夫排列。](#17. 约瑟夫排列:我们可以在此基础上定义约瑟夫排列,编号为 1, 2, ..., n 的 n 个人围成一个圈,每 m 个人移出一个人,直到移出所有的人。移出的顺序我们称为 (n, m)-约瑟夫排列。比如 (7, 3)-约瑟夫排列为 < 3, 6, 2, 7, 5, 1, 4>。请写一个程序,打印 (n, m)-约瑟夫排列。)
- [18. 什么是指针,什么是指针变量?](#18. 什么是指针,什么是指针变量?)
- [19. 什么是野指针,对野指针进行解引用运算会发生什么现象?](#19. 什么是野指针,对野指针进行解引用运算会发生什么现象?)
- [20. 指针可以进行哪些算术运算?](#20. 指针可以进行哪些算术运算?)
- [21. 矩阵乘法。写一个函数实现两个矩阵相乘。](#21. 矩阵乘法。写一个函数实现两个矩阵相乘。)
- [22. 请总结一下指针和数组之间的关系。](#22. 请总结一下指针和数组之间的关系。)
- [23. 删除有序数组中的连续重复元素 (保留一个)。](#23. 删除有序数组中的连续重复元素 (保留一个)。)
- [24. 回型填充数组: 输入一个整数 n (n <= 10),构造一个 n*n 方阵,方阵数值从1开始递增,按照"右 下 左 上"的方式循环向内填充。](#24. 回型填充数组: 输入一个整数 n (n <= 10),构造一个 n*n 方阵,方阵数值从1开始递增,按照“右 下 左 上”的方式循环向内填充。)
- [25. (a) 字符串变量的初始化和字符指针的初始化有何不同?请举例说明。](#25. (a) 字符串变量的初始化和字符指针的初始化有何不同?请举例说明。)
- [26. 利用 getchar() 实现类似 gets_s 的功能。](#26. 利用 getchar() 实现类似 gets_s 的功能。)
- [27. 将包含字符数字的字符串分开,使得分开后的字符串前一部分是数字,后一部分是字母。](#27. 将包含字符数字的字符串分开,使得分开后的字符串前一部分是数字,后一部分是字母。)
- [28 .将字符串中的空格替换成`"%020"` 。](#28 .将字符串中的空格替换成
"%020"
。) - [29. 删除字符串中指定的字符。](#29. 删除字符串中指定的字符。)
1. break 语句和 continue 语句有什么区别?
break
语句和continue
语句都是控制流语句,用于改变循环的执行顺序,但它们有不同的作用:
-
break
语句用于立即退出循环,无论循环条件是否满足。当程序执行到break
语句时,循环会立即终止,并且程序将继续执行循环后面的代码。break
语句通常用于在达到某个条件时提前结束循环。 -
continue
语句用于跳过当前循环中剩余的代码,立即进入下一次循环的迭代。当程序执行到continue
语句时,会立即停止本次循环的执行,并开始下一次循环。continue
语句通常用于在某些特定条件下跳过本次循环的执行。
2. 使用 goto 计算 1-2+3-4+...+99-100 的值。
c
int main(void) {
int sum = 0, i = 1;
loop:
if (i & 0x1) {
sum += i;
} else {
sum -= i;
}
i++;
if (i <= 100) {
goto loop;
}
printf("sum = %d\n", sum);
return 0;
}
3. 输入一个整数,判断这个数是不是素数。
sql
bool is_prime(int n) {
for (int i = 2; i * i < n; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
4. 写程序生成前 100 个素数。
c
#include <stdio.h>
#include <stdbool.h>
int main(void) {
int prime[100] = { 2 };
int n = 1, candidate = 3;
while (n < 100) {
bool isPrime = true;
for (int i = 0; i < n; i++) {
if (candidate % prime[i] == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
prime[n++] = candidate;
}
candidate++;
}
for (int i = 0; i < 100; i++) {
printf("%d ", prime[i]);
}
printf("\n");
return 0;
}
5
在密码学领域,对大整数进行素数测试是一个常见的问题。Fermat's Test(费马测试)是一种基于费马小定理的素数测试算法,它可以用来快速判断一个数是否可能为素数。费马小定理表述如下:
如果 p p p 是一个素数,且 a a a 是不可被 p p p 整除的任意整数,则 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod{p} ap−1≡1(modp)。
Fermat's Test 利用了这一定理:对于一个给定的数 n n n,选择一个随机整数 a a a,然后检查是否满足 a n − 1 ≡ 1 ( m o d n ) a^{n-1} \equiv 1 \pmod{n} an−1≡1(modn)。如果不满足,则 n n n 肯定不是素数;如果满足,则有可能是素数。通过多次选择不同的 a a a 进行测试,可以提高判断的准确性。
需要注意的是,虽然 Fermat's Test 能够快速判断一个数是否可能为素数,但并不是绝对准确的。存在一些伪素数(pseudoprime),它们能够通过 Fermat's Test 的检验,但实际上并不是素数。因此,在实际应用中,为了提高安全性,通常会结合其他素数测试方法来进行判断。
6. (a) 对于一个大小为N的数组,它的下标范围是多少?
对于一个大小为N的数组,它的下标范围是从0到N-1。数组的下标从0开始,到N-1结束,共计N个元素。
7. 如何计算arr[i]
的地址?
要计算arr[i]
的地址,可以使用以下公式:
address_of_arr_i = address_of_arr + i * size_of_each_element
其中,address_of_arr
是数组arr
的起始地址,i
是要访问的元素的下标,size_of_each_element
是数组中每个元素的大小(以字节为单位)。
例如,如果要计算arr[3]
的地址,假设arr
的起始地址是0x1000
,每个元素占4个字节(int类型),则计算公式为:
address_of_arr_3 = 0x1000 + 3 * 4 = 0x100C
因此,arr[3]
的地址是0x100C
。
8. 为什么程序员会有这样一个刻板印象:数组的效率比链表高?
程序员通常有这样一个刻板印象是因为数组和链表在访问和操作上有不同的性能特点,使得它们在不同的场景下具有不同的效率。
-
内存访问方式:数组在内存中是连续存储的,而链表的节点可以是分散存储的。在访问数组元素时,可以通过索引直接计算出元素的地址并进行访问,而在链表中,需要从头节点开始逐个遍历找到目标节点,因此数组的访问效率更高。
-
缓存友好性:现代计算机系统中,缓存起着非常重要的作用。由于数组元素在内存中是连续存储的,所以在访问数组时,可以更好地利用缓存预取机制,提高访问效率。而链表节点分散存储,可能会导致缓存未命中,降低访问效率。
-
插入和删除操作:在插入和删除操作上,链表比数组更高效。因为数组在插入和删除时需要移动大量元素,而链表只需要修改指针即可完成操作。
-
空间利用率:对于数组来说,需要预先分配一定大小的内存空间,而且大小固定。而链表可以动态分配内存,根据需要动态增长,因此在空间利用率上,链表更灵活。
综上所述,虽然数组和链表各有优势,但在大多数情况下,程序员更倾向于使用数组来实现一些数据结构,因为数组在访问上更高效,尤其是对于需要频繁访问元素的情况。
9. 写一个能计算数组大小的宏函数。
你可以使用以下宏函数来计算数组的大小:
c
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
这个宏函数使用了 sizeof
运算符来计算整个数组的大小,然后除以单个元素的大小,从而得到数组的元素个数。注意,这个宏函数只能用于在同一作用域内定义的数组,因为它依赖于编译器对数组的大小计算。
10. 写一个随机发牌的小程序:用户指定发几张牌,程序打印手牌。
输入:
Enter number of cards in hand: 5
输出:
Your hand: 7c 2s 5d as 2h
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define NUM_SUITS 4
#define NUM_RANKS 13
int main(void) {
bool inHand[NUM_SUITS][NUM_RANKS] = { false };
const char suits[NUM_SUITS] = { 's', 'h', 'c', 'd' };
const char ranks[NUM_RANKS] = { '2', '3', '4', '5', '6', '7', '8', '9', 't', 'j', 'q', 'k', 'a' };
int n;
printf("Enter number of cards in hand: ");
scanf("%d", &n);
srand((unsigned)time(NULL));
while (n > 0) {
int suit = rand() % NUM_SUITS;
int rank = rand() % NUM_RANKS;
if (!inHand[suit][rank]) {
inHand[suit][rank] = true;
n--;
printf("%c%c ", ranks[rank], suits[suit]);
}
}
printf("\n");
return 0;
}
11. 为什么传递数组时,除了要传入数组名,还要传入长度?
在C语言中,数组作为函数参数传递时,只传递数组名是不够的,因为数组名本身并不包含数组的长度信息。数组在内存中是一段连续的存储空间,而数组名只是数组首元素的地址,无法确定数组的长度。
因此,为了在函数内部正确地处理数组,需要额外传递数组的长度信息。这样函数才能知道数组的实际长度,避免越界访问等问题。通常情况下,可以将数组长度作为函数参数传递,或者使用特殊的约定,比如约定数组的最后一个元素为特定的值(如NULL),以表示数组的结束。
12. 二维数组arr[M][N]
的本质是一个一维数组,这个一维数组有多少个元素,每个元素的类型是什么?arr[i][j]
的地址是多少?为什么二维数组传递的时候,不能省略第二个维度?
二维数组arr[M][N]
本质上是一个包含了M
个元素的一维数组,每个元素是一个包含了N
个元素的子数组。因此,二维数组arr[M][N]
一共有M * N
个元素,每个元素的类型是数组arr
的第二维的类型,即arr
的第二维是一个包含了N
个元素的数组,因此每个元素的类型是包含了N
个元素的数组类型,可以表示为arr[N]
。
对于arr[i][j]
的地址,可以使用以下公式计算:
address_of_arr_ij = address_of_arr + i * N * size_of_each_element + j * size_of_each_element
其中,address_of_arr
是数组arr
的起始地址,size_of_each_element
是每个元素的大小(以字节为单位),i
和j
分别是元素在第一维和第二维的下标。
关于为什么二维数组传递的时候不能省略第二个维度,原因是C语言中数组作为函数参数传递时,数组的第一维度可以省略,但第二维度必须指定。这是因为在函数参数中,数组名会被转换为指向数组首元素的指针,因此数组名本身并不包含数组的大小信息。为了在函数内部正确处理二维数组,需要知道第二维度的大小。
13. 什么是局部变量,什么是外部变量,它们的作用域分别是什么?
局部变量(local variable)是在函数或代码块内部定义的变量,它的作用域仅限于定义它的函数或代码块内部。局部变量在函数或代码块执行结束后会被销毁,不能在函数或代码块外部访问。
外部变量(external variable)是在函数或代码块外部定义的变量,它的作用域从定义处开始,直到文件结束或被另一个作用域覆盖为止。外部变量可以在文件中的任何地方被访问,但是如果在函数内部使用外部变量,则需要使用extern
关键字进行声明。
总结一下:
- 局部变量的作用域仅限于定义它的函数或代码块内部。
- 外部变量的作用域从定义处开始,直到文件结束或被另一个作用域覆盖为止。
- 外部变量可以在文件中的任何地方被访问,而局部变量只能在定义它的函数或代码块内部被访问。
14. 存储期限有哪些?局部变量默认的存储期限是什么?怎么改变局部变量的存储期限?
存储期限(storage duration)指的是变量在程序运行过程中所占用内存的持续时间,主要有以下几种:
-
静态存储期限(static storage duration):变量在程序运行期间始终存在,直到程序结束才会被销毁。静态变量(包括全局变量和静态局部变量)具有静态存储期限。
-
自动存储期限(automatic storage duration):变量在进入声明它的代码块时被创建,在离开代码块时被销毁。局部变量具有自动存储期限。
-
动态存储期限(dynamic storage duration) :变量在程序运行时显式地分配内存,并在不再需要时显式地释放内存。动态存储期限的变量通过调用
malloc
、calloc
或new
等函数来分配内存。
局部变量默认具有自动存储期限。如果要改变局部变量的存储期限,可以将其声明为static
或extern
:
- 将局部变量声明为
static
,使其具有静态存储期限。静态局部变量在第一次进入声明它的代码块时初始化,且只初始化一次,在函数调用结束后不会被销毁,而是保留其值直到程序结束。 - 将局部变量声明为
extern
,使其具有外部链接性,这样它的存储期限和作用域都会扩展到整个文件。
15. 德州扑克:写一个程序循环读取 5 张手牌 (输入 0 结束程序),然后把手中的牌分为下面某一类:1.同花顺 2.四张 3.葫芦 (3 + 2) 4. 同花 5. 顺子 6.三张 7.两对 8. 一对 9.高牌。程序对话如下:
Enter a card: 2s
Enter a card: 5s
Enter a card: 4s
Enter a card: 3s
Enter a card: 6s
Straight flush
Enter a card: 8c
Enter a card: as
Enter a card: 8c
Duplicate card; ignored.
Enter a card: 7c
Enter a card: ad
Enter a card: 3h
Pair
Enter a card: 6s
Enter a card: d2
Bad card; ignored.
Enter a card: 2d
Enter a card: 9c
Enter a card: 4h
Enter a card: ts
High card
Enter a card: 0
c
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_RANKS 13
#define NUM_SUITS 4
#define NUM_CARDS 5
int num_in_rank[NUM_RANKS];
int num_in_suit[NUM_SUITS];
bool straight, flush, four, three;
int pairs; /* can be 0, 1, or 2 */
void read_cards(void);
void analyze_hand(void);
void print_result(void);
int main(void)
{
for (;;) {
read_cards();
analyze_hand();
print_result();
}
}
void read_cards(void)
{
bool card_exists[NUM_RANKS][NUM_SUITS];
char ch, rank_ch, suit_ch;
int rank, suit;
bool bad_card;
int cards_read = 0;
for (rank = 0; rank < NUM_RANKS; rank++) {
num_in_rank[rank] = 0;
for (suit = 0; suit < NUM_SUITS; suit++)
card_exists[rank][suit] = false;
}
for (suit = 0; suit < NUM_SUITS; suit++)
num_in_suit[suit] = 0;
while (cards_read < NUM_CARDS) {
bad_card = false;
printf("Enter a card: ");
rank_ch = getchar();
switch (rank_ch) {
case '0': exit(EXIT_SUCCESS);
case '2': rank = 0; break;
case '3': rank = 1; break;
case '4': rank = 2; break;
case '5': rank = 3; break;
case '6': rank = 4; break;
case '7': rank = 5; break;
case '8': rank = 6; break;
case '9': rank = 7; break;
case 't': case 'T': rank = 8; break;
case 'j': case 'J': rank = 9; break;
case 'q': case 'Q': rank = 10; break;
case 'k': case 'K': rank = 11; break;
case 'a': case 'A': rank = 12; break;
default: bad_card = true;
}
suit_ch = getchar();
switch (suit_ch) {
case 'c': case 'C': suit = 0; break;
case 'd': case 'D': suit = 1; break;
case 'h': case 'H': suit = 2; break;
case 's': case 'S': suit = 3; break;
default: bad_card = true;
}
// read remaining characters of this line
while ((ch = getchar()) != '\n')
if (ch != ' ') bad_card = true;
if (bad_card)
printf("Bad card; ignored.\n");
else if (card_exists[rank][suit])
printf("Duplicate card; ignored.\n");
else {
num_in_rank[rank]++;
num_in_suit[suit]++;
card_exists[rank][suit] = true;
cards_read++;
}
}
}
void analyze_hand(void)
{
int num_consec = 0;
int rank, suit;
straight = false;
flush = false;
four = false;
three = false;
pairs = 0;
/* check for flush */
for (suit = 0; suit < NUM_SUITS; suit++)
if (num_in_suit[suit] == NUM_CARDS)
flush = true;
/* check for straight */
rank = 0;
while (num_in_rank[rank] == 0) rank++;
for (; rank < NUM_RANKS && num_in_rank[rank] > 0; rank++)
num_consec++;
if (num_consec == NUM_CARDS) {
straight = true;
return;
}
/* check for 4-of-a-kind, 3-of-a-kind, and pairs */
for (rank = 0; rank < NUM_RANKS; rank++) {
if (num_in_rank[rank] == 4) four = true;
if (num_in_rank[rank] == 3) three = true;
if (num_in_rank[rank] == 2) pairs++;
}
}
void print_result(void)
{
if (straight && flush) printf("Straight flush");
else if (four) printf("Four of a kind");
else if (three && pairs == 1) printf("Full house");
else if (flush) printf("Flush");
else if (straight) printf("Straight");
else if (three) printf("Three of a kind");
else if (pairs == 2) printf("Two pairs");
else if (pairs == 1) printf("Pair");
else printf("High card");
printf("\n\n");
}
16. 约瑟夫环:n 个人站成一圈,每个 m 个人处决一个人。假设这 n 个人的编号为 1, 2, ..., n,并且从 1 开始数,问最终活下来的人编号是多少?
约瑟夫环(Josephus problem)是一个经典的数学问题,描述如下:n个人围成一圈,从第一个人开始报数,报到第m个人的时候,该人被杀死;然后下一个人继续报数,直到剩下最后一个人。问最后一个活着的人原来的编号是多少。
有一个经典的解法使用递归来求解,可以表示为:
plaintext
f(n, m) = (f(n - 1, m) + m) % n
f(1, m) = 0
其中,f(n, m) 表示 n 个人中最后存活的人的编号,m 表示每次报数到第 m 个人被杀死。
下面是一个使用递归解决约瑟夫环问题的 C 代码示例:
c
int josephus(int n, int m) {
if (n == 1) {
return 0;
} else {
return (josephus(n - 1, m) + m) % n;
}
}
int main() {
int n = 5; // 5 个人
int m = 2; // 每次报数到第 2 个人被杀死
printf("最后活下来的人的编号是:%d\n", josephus(n, m) + 1); // 注意编号是从 1 开始的
return 0;
}
这段代码中,josephus 函数用来计算最后活下来的人的编号,main 函数用来测试该函数。
17. 约瑟夫排列:我们可以在此基础上定义约瑟夫排列,编号为 1, 2, ..., n 的 n 个人围成一个圈,每 m 个人移出一个人,直到移出所有的人。移出的顺序我们称为 (n, m)-约瑟夫排列。比如 (7, 3)-约瑟夫排列为 < 3, 6, 2, 7, 5, 1, 4>。请写一个程序,打印 (n, m)-约瑟夫排列。
这里是一个打印 (n, m)-约瑟夫排列的简单程序,使用了循环来模拟移出人的过程:
c
#include <stdio.h>
void josephus_permutation(int n, int m) {
int i, j, count;
int people[n];
for (i = 0; i < n; i++) {
people[i] = i + 1;
}
printf("<");
count = n;
i = 0;
while (count > 0) {
for (j = 1; j < m; j++) {
while (people[i] == 0) {
i = (i + 1) % n;
}
i = (i + 1) % n;
}
while (people[i] == 0) {
i = (i + 1) % n;
}
printf("%d", people[i]);
count--;
if (count > 0) {
printf(", ");
}
people[i] = 0;
}
printf(">\n");
}
int main() {
int n = 7;
int m = 3;
josephus_permutation(n, m);
return 0;
}
这个程序首先创建了一个数组 people
来表示每个人的编号,然后使用循环来模拟移出人的过程。在每一轮中,根据规则移出一个人,并将该人的编号打印出来,直到所有人都被移出。
18. 什么是指针,什么是指针变量?
指针(pointer)是一个变量,其值为另一个变量的内存地址。简单说,指针是用来存储内存地址的变量。指针可以指向任何数据类型(整数、字符、数组、函数等)的内存地址。
指针变量(pointer variable)是存储内存地址的变量。它包含了一个内存地址,可以指向内存中的某个位置,从而可以通过该地址访问或修改存储在该位置的数据。
例如,在C语言中,可以通过以下方式定义一个整型指针变量:
c
int *ptr;
这里ptr
是一个指针变量,可以指向一个整型变量的地址。
19. 什么是野指针,对野指针进行解引用运算会发生什么现象?
野指针(dangling pointer)是指指向已经释放或者无效的内存地址的指针。野指针可能会导致程序运行时的不确定行为,因为它们指向的内存地址可能已经被其他程序使用,或者已经被系统回收。
对野指针进行解引用运算会导致未定义的行为,因为解引用操作实际上是试图访问指针所指向的内存地址的内容,而野指针指向的内存地址可能已经被其他程序使用或者已经被系统回收,这样就会导致访问无效的内存地址,可能会导致程序崩溃或者产生不可预料的结果。
为了避免野指针问题,应该始终确保指针指向有效的内存地址,避免在指针指向的内存被释放或者无效后继续使用该指针。
20. 指针可以进行哪些算术运算?
指针可以进行以下几种算术运算:
-
指针加法 :可以将一个整数加到指针上,以移动指针指向的地址。例如,
ptr + 1
将指针移动到下一个地址位置。 -
指针减法 :可以将一个整数从指针上减去,以移动指针指向的地址。例如,
ptr - 1
将指针移动到前一个地址位置。 -
指针与指针之间的减法 :可以将两个指针相减,得到它们之间相差的元素个数。例如,
(ptr2 - ptr1)
将返回两个指针之间相差的元素个数。 -
比较运算:可以对两个指针进行比较运算,判断它们是否指向同一个位置或者哪一个指针指向的位置在另一个之前。
21. 矩阵乘法。写一个函数实现两个矩阵相乘。
以下是一个简单的C程序,实现了两个矩阵的乘法运算:
c
#include <stdio.h>
#define ROW1 2
#define COL1 3
#define ROW2 3
#define COL2 2
void matrix_multiply(int mat1[][COL1], int mat2[][COL2], int result[][COL2]) {
int i, j, k;
for (i = 0; i < ROW1; i++) {
for (j = 0; j < COL2; j++) {
result[i][j] = 0;
for (k = 0; k < COL1; k++) {
result[i][j] += mat1[i][k] * mat2[k][j];
}
}
}
}
void display_matrix(int mat[][COL2], int row, int col) {
int i, j;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
printf("%d ", mat[i][j]);
}
printf("\n");
}
}
int main() {
int mat1[ROW1][COL1] = {{1, 2, 3}, {4, 5, 6}};
int mat2[ROW2][COL2] = {{7, 8}, {9, 10}, {11, 12}};
int result[ROW1][COL2];
matrix_multiply(mat1, mat2, result);
printf("Matrix 1:\n");
display_matrix(mat1, ROW1, COL1);
printf("\nMatrix 2:\n");
display_matrix(mat2, ROW2, COL2);
printf("\nResult Matrix:\n");
display_matrix(result, ROW1, COL2);
return 0;
}
这个程序定义了两个矩阵mat1
和mat2
,分别是一个2x3和一个3x2的矩阵,然后调用matrix_multiply
函数计算它们的乘积,并将结果存储在result
矩阵中。最后,通过display_matrix
函数显示原始矩阵和结果矩阵。
22. 请总结一下指针和数组之间的关系。
指针和数组在C语言中有着密切的关系,可以总结如下:
-
数组名是指向数组第一个元素的指针 :在大多数情况下,数组名被编译器解释为指向数组第一个元素的指针。例如,对于数组
int arr[5]
,arr
可以被视为&arr[0]
,即指向第一个元素的指针。 -
指针可以像数组一样进行偏移 :指针可以通过加法和减法运算来访问数组中的元素。例如,
ptr + 1
将指向数组中的下一个元素。 -
数组名不能进行赋值 :由于数组名被视为指向常量的指针,因此不能将数组名用于赋值操作。例如,
arr = &some_other_array[0]
是非法的。 -
指针可以被用来遍历数组 :通过递增指针来遍历数组是一种常见的方法。例如,可以使用
for
循环和指针来遍历数组中的所有元素。 -
数组名作为函数参数时会被转换为指针:当将数组名作为参数传递给函数时,实际上传递的是指向数组第一个元素的指针,而不是整个数组。
23. 删除有序数组中的连续重复元素 (保留一个)。
c
int removeDuplicates(int* arr, int n){
int i, j = 1;
for (i = 1; i < n; i++) {
if (arr[i] != arr[j-1]) {
arr[j] = arr[i];
j++;
}
}
return j;
}
24. 回型填充数组: 输入一个整数 n (n <= 10),构造一个 n*n 方阵,方阵数值从1开始递增,按照"右 下 左 上"的方式循环向内填充。
你可以使用如下的 C 代码来实现回型填充数组的功能:
c
#include <stdio.h>
void fillMatrix(int n, int mat[][n]) {
int value = 1;
int top = 0, bottom = n - 1, left = 0, right = n - 1;
int direction = 0; // 0: right, 1: down, 2: left, 3: up
while (top <= bottom && left <= right) {
if (direction == 0) {
for (int i = left; i <= right; i++) {
mat[top][i] = value++;
}
top++;
} else if (direction == 1) {
for (int i = top; i <= bottom; i++) {
mat[i][right] = value++;
}
right--;
} else if (direction == 2) {
for (int i = right; i >= left; i--) {
mat[bottom][i] = value++;
}
bottom--;
} else if (direction == 3) {
for (int i = bottom; i >= top; i--) {
mat[i][left] = value++;
}
left++;
}
direction = (direction + 1) % 4;
}
}
void printMatrix(int n, int mat[][n]) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("%d\t", mat[i][j]);
}
printf("\n");
}
}
int main() {
int n;
printf("Enter the value of n (n <= 10): ");
scanf("%d", &n);
int mat[n][n];
fillMatrix(n, mat);
printf("The filled matrix is:\n");
printMatrix(n, mat);
return 0;
}
这段代码中,fillMatrix
函数实现了回型填充数组的逻辑,printMatrix
函数用于打印填充后的数组。在main
函数中,用户输入矩阵的大小 n
,然后调用fillMatrix
和printMatrix
函数来填充和打印矩阵。
25. (a) 字符串变量的初始化和字符指针的初始化有何不同?请举例说明。
在 C 语言中,字符串变量的初始化和字符指针的初始化有一些不同。
-
字符串变量的初始化:字符串变量可以使用字符串常量来初始化。在 C 语言中,字符串常量是由双引号括起来的字符序列。例如:
cchar str[] = "Hello, World!";
这里的
str
是一个字符数组,它会被初始化为包含字符串常量 "Hello, World!" 的内容。 -
字符指针的初始化:字符指针可以指向字符串常量,也可以指向字符数组。指向字符串常量的字符指针可以通过直接赋值来初始化。例如:
cchar *ptr = "Hello, World!";
这里的
ptr
是一个字符指针,它指向字符串常量 "Hello, World!" 的第一个字符。需要注意的是,字符串常量是不可修改的,所以尽管ptr
是一个指针,但不能通过它来修改字符串的内容。
另外,如果要使用字符指针指向字符数组来初始化,可以直接将数组名赋值给指针,因为数组名会被解释为指向数组第一个元素的指针。例如:
c
char str[] = "Hello, World!";
char *ptr = str; // ptr 指向字符数组 str
这样,ptr
就指向了数组 str
的第一个元素。
26. 利用 getchar() 实现类似 gets_s 的功能。
可以使用getchar()
函数逐字符读取输入,并将读取的字符存储到一个字符数组中,直到遇到换行符为止,从而实现类似gets_s
函数的功能。以下是一个示例代码:
c
#include <stdio.h>
void my_gets_s(char *str, int size) {
int i = 0;
char c;
// 读取字符,直到遇到换行符或者数组满了为止
while ((c = getchar()) != '\n' && i < size - 1) {
str[i++] = c;
}
// 添加字符串结束标志
str[i] = '\0';
}
int main() {
char str[100];
printf("Enter a string: ");
my_gets_s(str, sizeof(str));
printf("You entered: %s\n", str);
return 0;
}
在这个示例中,my_gets_s
函数模拟了gets_s
函数的行为,通过getchar()
逐字符读取输入,并将字符存储到字符数组str
中,直到遇到换行符或者数组满了为止。然后在数组末尾添加字符串结束标志\0
,以构成一个完整的字符串。
27. 将包含字符数字的字符串分开,使得分开后的字符串前一部分是数字,后一部分是字母。
输入: "h1ell2o3"
输出: "123hello"
c
void seperate(char* str) {
char digits[N], alphas[N];
int i = 0, j = 0;
char* p = str;
while (*p) {
if (isdigit(*p)) {
digits[i++] = *p;
} else {
alphas[j++] = *p;
}
p++;
}
digits[i] = '\0';
alphas[j] = '\0';
strcpy(str, digits);
strcat(str, alphas);
}
28 .将字符串中的空格替换成"%020"
。
c
输入: "hello world how "
输出: "hello%020world%020how%020"
c
void substitute_space(char* str) {
char substitute[N];
int i = 0;
char* p = str;
while (*p) {
if (*p == ' ') {
substitute[i] = '%';
substitute[i + 1] = '0';
substitute[i + 2] = '2';
substitute[i + 3] = '0';
i += 4;
} else {
substitute[i++] = *p;
}
p++;
}
substitute[i] = '\0';
strcpy(str, substitute);
}
29. 删除字符串中指定的字符。
输入: "abcdaefaghiagkl", 'a'
输出: "bcdefghigkl"
c
void delete_character(char* str, char ch) {
char tmp[N];
int i = 0;
char* p = str;
while (*p) {
if (*p != ch) {
tmp[i++] = *p;
}
p++;
}
tmp[i] = '\0';
strcpy(str, tmp);
}