C 编程

#include<stdio.h>
#define a5+2

int main() {
    int ans;
    ans = a * a * a; 
    printf("%d", c); 
    return 0; 
}

初学练习 - 解决方案

变量命名

  1. 举一个不合法的 C 变量名示例,并说明为什么它不合法?

不合法的示例:#nm*rt

原因:变量名必须以字母(大小写)或下划线开始,且只能包含字母、数字和下划线。像 #* 这样的字符是不允许用于变量名中的。

示例代码

#include <stdio.h>
#include <stdlib.h>

int main() {
    int a, b, c, max;
    #ifdef __WIN32
        system("cls"); // Windows 环境下清屏
    #else
        system("clear"); // Unix 和 Linux 环境下清屏
    #endif
    
    printf("\n请输入三个数字:");
    scanf("%d %d %d", &a, &b, &c);
    
    max = a;
    if (max < b)
        max = b;
    if (max < c)
        max = c;
    
    printf("\n最大值 = %d\n", max);
    getchar();
}

数据类型

1.1. 在你的计算机上,每种数据类型占用多少内存?

在我的计算机上:

  • long int:占用 4 字节
  • short int:占用 2 字节
  • float:占用 4 字节

我们不能使用 intfloat 作为变量名。

赋值

标准的赋值方式是:

double pi;
pi = 3.14;

由于 pi 是常量,良好的编程规范是将其设为在运行时不可更改。附加学分,如果使用以下两行之一:

const float pi = 3.14;
#define pi 3.14

例如:

int a = 67;
double b;
b = a;

是的,但需要进行类型转换,并且 double 会被截断:

double a = 89;
int b;
b = (int) a;

引用

pi2 = pi;

相反的 pi = pi2; 是一个有效的 C 语句,前提是 pi 不是常量,并且 pi2 已经初始化。

a. pi2 = 3.1415;

b. 反向操作 3.1415 = pi2; 是不合法的,因为不能将值赋给常量。

简单 I/O 操作

字符串操作

一个可能的解决方案如下:

#include <stdio.h>
#include <string.h>

int main(void) {
    char s[81]; // 最多 80 个字符 + '\0'
    int i;

    puts("请输入一个字符串:");
    fgets(s, 81, stdin);

    puts("你的句子反转后的结果是:");
    for (i = strlen(s) - 1; i >= 0; i--) {
        if (s[i] == '\n')
            continue; // 不输出换行符
        else
            putchar(s[i]);
    }
    putchar('\n');
    return 0;
}

解释

这段代码的功能是接受一个输入字符串,然后将字符串反转并输出。主要通过 fgets() 函数读取用户输入的字符串,通过 strlen() 计算字符串的长度,并使用一个反向循环输出字符串的每个字符(跳过换行符)。

C 代码示例翻译

#define __STDC_WANT_LIB_EXT1__ 1 // 启用 C11 扩展函数(此示例为 gets_s)
#include<stdio.h>

int slen(const char *); // 我不想仅为了获取字符串长度而包含整个字符串库

int main(void) {
  char s[500];
  printf("%s", "请输入一个句子: ");
  if (!gets_s(s, 500)) {
    puts("错误!");
    getchar();
    return 0;
  }
  for (int i = 0; i < slen(s); i++)
    if (s[i] != 32)
      putchar(s[i]);
    else
      putchar(10); // 换行符
  putchar(10);
  getchar();
  return 0;
}

int slen(const char *s) {
  int i;
  for (i = 0; s[i] != 0; i++);
  return i;
}

循环示例

  1. 打印三角形的星号
#include<stdio.h>

int main(void) {
  int n;
  scanf("%d", &n);
  for (int i = 0; i < n; i++) {
    for (int j = 0; j <= i; j++)
      putchar(42); // 42 是 ASCII 中 '*' 的值
    putchar(10); // 换行
  }
  while ((n = getchar()) != 10); // 等待用户输入
  getchar(); // 用于捕获换行符
  return 0;
}
  1. 打印倒三角形的星号
#include<stdio.h>
// 这是最快的方法
int main(void) {
  int n;
  scanf("%d", &n);
  for (int i = 0; i < n; i++) {
    for (int j = 0; j <= i; j++)
      putchar(42); // 打印星号
    putchar(10); // 换行
  }
  for (int i = n - 1; i > 0; i--) { // 打印倒三角形
    for (int j = 0; j < i; j++)
      putchar(42); // 打印星号
    putchar(10); // 换行
  }
  while ((n = getchar()) != 10); // 等待用户输入
  getchar(); // 用于捕获换行符
  return 0;
}
  1. 通过数学运算实现形状打印
void sideways(int n)
{
    int i = 0, j = 0;
    for (i = 1; i < 2 * n; i++) {
        for (j = 1; j <= (n - (abs(n - i))); j++) {
            printf("*");
        }
        printf("\n");
    }
}
  1. 打印上升的菱形
void right_side_up(int n)
{
    int x, y;
    for (y = 1; y <= n; y++) {
        for (x = 0; x < n - y; x++)
            putchar(' '); // 打印空格
        for (x = (n - y); x < (n - y) + (2 * y - 1); x++)
            putchar('*'); // 打印星号
        putchar('\n'); // 换行
    }
}

解释

  1. gets_sslen 示例:该代码要求用户输入一个句子并将其逆序输出。gets_s 是 C11 标准中用于安全获取字符串的函数,避免了 gets 的潜在危险。slen 函数是用来计算字符串长度的自定义函数,它不依赖于标准库的 strlen 函数。

  2. 打印三角形和倒三角形:这些示例使用了循环结构来打印不同形状的星号。外层循环负责控制行数,内层循环控制每行打印的星号数量。

  3. 数学运算形状打印:通过数学公式和循环,打印出菱形和倒菱形的形状。这些代码示例展示了如何使用 abs 函数和合适的循环嵌套来生成图形。

这些代码示例展示了如何在 C 语言中使用基本的输入输出、字符串处理以及循环结构来完成一些简单的任务。

另一种解决方案:

#include<stdio.h>

int main(void) {
  int n;
  scanf("%d", &n);
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n - i - 1; j++)
      putchar(32);  // 打印空格
    for (int j = 0; j < i * 2 + 1; j++)
      putchar(42);  // 打印星号
    putchar(10);  // 换行
  }
  while ((n = getchar()) != 10);
  getchar();
  return 0;
}

数学

// 编译命令: gcc -Wall prime.c -lm -o prime

#include <math.h>    // 用于平方根函数 sqrt()	
#include <stdio.h>

int is_prime(int n);

int main() 
{
  printf("输入一个整数: ");
  int var;
  scanf("%d", &var);
  if (is_prime(var) == 1) {
    printf("是质数\n");
  } else {
    printf("不是质数\n");
  }
  return 1;
}

int is_prime(int n)
{
  int x;
  int sq = sqrt(n) + 1;
 
  // 先检查简单的情况
  if (n < 2)
    return 0;
  if (n == 2 || n == 3)
    return 1;
 
  // 检查 n 是否能被 2 或者 3 以外的奇数整除
  if (n % 2 == 0)
    return 0;
  for (x = 3; x <= sq; x += 2) {
    if (n % x == 0)
      return 0;
  }
 
  return 1; 
}

另一种更好的解决方案,不需要包含 math.h,并且比上面的方法更快。

#include<stdio.h>

int isPrime(const unsigned long long int);

int main(void) {
  unsigned long long n;
  scanf("%llu", &n);
  printf("%d\n", isPrime(n));
  while ((n = getchar()) != 10);
  getchar();
  return 0;
}

int isPrime(const unsigned long long int x) {
  if (x < 4)
    return x > 1;
  if (x % 2 == 0 || x % 3 == 0)
    return 0;
  for (unsigned long int i = 5; i * i <= x; i += 6)
    if (x % i == 0 || x % (i + 2) == 0)
      return 0;
  return 1;
}

这段代码提供了不同的方法来判断一个数字是否为质数,第二个版本没有使用 math.h,通过更高效的方式进行质数判定。

递归
归并排序
一种可能的解决方案,在阅读了在线描述的递归归并排序后,例如 Dasgupta 的描述:

// 编译命令: gcc -Wall rmergesort.c -lm -o rmergesort

/*
 ============================================================================
 Name        : rmergesort.c
 Author      : Anon
 Version     : 0.1
 Copyright   : (C)2013 under CC-By-SA 3.0 License
 Description : 递归归并排序,Ansi 风格
 ============================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//const int MAX = 200;
const int MAX = 20000000;

int *b;

int printOff = 0;

// 这个调试打印函数有助于展示数组当前的状态
void printArray(char* label, int* a, int sz) {
	int h = sz/ 2;
	int i;

	if (printOff) return;

	printf("\n%s:\n", label);

	for (i = 0; i < h; ++i ) {

		printf("%d%c", a[i],
					// 每 20 个数字换行
					( ( i != 0 && i % 20 == 0 )? '\n': ' ' ) );
	}

	printf(" | ");
	for (; i < sz; ++i) {
		printf("%d%c", a[i],
					( ( i != 0 && i % 20 == 0 )? '\n': ' ' ) );
	}

	putchar('\n');
}

void mergesort(int* a, int m ) {

	printArray("BEFORE", a, m);

	if (m > 2) {
		// 如果元素个数大于 2,则递归处理
		mergesort(a, m / 2);
		mergesort(a + m / 2, m - m / 2);
	} else if (m == 2 && a[0] > a[1]) {
		// 如果有 2 个元素,并且需要交换,进行交换
		int t = a[1];
		a[1] = a[0];
		a[0] = t;
		goto end;
	}

	// 1 或者大于 2 个已经排序好的元素

	// 将数组分为左右两个子数组
	// 并合并到数组 b 中
	int n = m / 2;
	int o = m - n;

	int i = 0, j = n;
	int l = 0;
	// i 是左子数组,j 是右子数组;
	// l 应该等于 m,即数组的大小
	while (i < n) {
		if (j >= m) {
			// 如果右子数组已经处理完,则复制剩下的左子数组
			for (; i < n; ++i) {
				b[l++] = a[i];
			}
		} else if (a[i] < a[j]) {
			// 合并操作,将较小的元素放入 b 中
			b[l++] = a[i++];
		} else {
			b[l++] = a[j++];
		}
	}

	while (j < m) {
		// 如果右子数组有剩余元素,复制到 b 中
		b[l++] = a[j++];
	}

	memcpy(a, b, sizeof(int) * l );

end:
	printArray("AFTER", a, m);

	return;
}

void rand_init(int* a, int n) {
	int i;
	for (i = 0; i < n; ++i) {
		a[i] = rand() % MAX;
	}
}

int main(void) {
	puts("!!!Hello World!!!"); /* 打印 !!!Hello World!!! */

	//int N = 20;
	//int N = 1000;
	//int N = 1000000;
	int N = 100000000;  // 在 Ubuntu 4GB,Phenom 上仍然不会导致栈溢出
	printOff = 1;

	int *a;

	b = calloc(N, sizeof(int));

	a = calloc(N, sizeof(int));

	rand_init(a, N);

	mergesort(a, N);

	printOff = 0;

	printArray("LAST", a, N);

	free(a);
	free(b);

	return EXIT_SUCCESS;
}

/* 在未能翻译我的非递归归并排序概念后,我开始尝试递归归并排序的解决方案。
下一步的任务是将递归版本转换为非递归版本。 这可以通过将对 mergesort 的调用替换为推送包含 <数组起始地址><处理元素数量> 的元组到栈中来完成。 */

/* 合并的核心思想是:
将两个已排序的列表合并成一个已排序的列表,方法是比较每个列表的顶部元素,将较小的元素移到新列表的末尾。
另一个具有较大元素的列表保持其顶部元素不变。当一个列表被消耗完时,将剩下的另一个列表的元素复制到新列表的末尾。 */

/* 递归的部分是通过将未排序的列表分成两个列表,直到只剩下 1 或 2 个元素, 如果有 2 个元素,则通过交换直接排序。 在递归返回后,合并这些列表,最终返回一个已排序的列表并返回给递归调用的父级,供进一步合并使用。 */

/* 以下是关于编程时程序员可能会思考的内容的假想讨论: 将递归可视化成 Z80 汇编语言(类似于大多数汇编语言),存在一个数据栈(DS)和一个调用栈(CS)指针,每个递归调用都会将返回地址(即调用后面的指令地址)推入 CS 指向的栈中,并递增 CS,同时将数组起始地址和子数组长度推入 DS 指向的数据栈中,数据栈会递增两次。

如果递归调用的次数超过了调用栈或数据栈允许的空间,则程序会崩溃,或者 CPU 会发送进程空间保护异常信号,跳转到中断处理程序。*/

二叉堆
10, 4, 6, 3, 5, 11 -> 10
4, 6, 3, 5, 11 -> 10, 4 :4 被添加到末尾,没有进行父子交换,因为 4 < 10。
6, 3, 5, 11 -> 10, 4, 6 :6 被添加到末尾,没有进行父子交换,因为 6 < 10。
3, 5, 11 -> 10, 4, 6, 3 :3 被添加到末尾,3 的位置是 4,除以 2 得到 2,位置 2 上是 4,没有进行父子交换,因为 4 > 3。
5, 11 -> 10, 4, 6, 3, 5 :5 被添加到末尾,5 的位置是 5,除以 2 得到 2,位置 2 上是 4,进行父子交换,因为 4 < 5;交换后 5 变到位置 2,未与 10 交换,因为 5 < 10。

  • 10, 5, 6, 3, 4

11 -> 10, 5, 6, 3, 4, 11 :11 被添加到末尾,11 的位置是 6,除以 2 得到 3,交换 6 和 11,11 的位置变为 3,再与 10 交换,停止交换,因为没有父节点。

  • 11, 5, 10, 3, 4, 6

  • 11 有子节点 5, 10;5 有子节点 3 和 4;10 有子节点 6。父节点总是大于子节点。

11 离开 * ,5, 10, 3, 4, 6 -> 6, 5, 10, 3, 4 -> 向下筛选 -> 选择较大的子节点 5(2n+0)或 10(2n+1) -> 6 是否大于 10?不是 -> 交换 10 和 6 ->

  • 10, 5, *6, 3, 4 -> 4 是最大的子节点,因为没有 +1 子节点。是否 6 大于 4?是的,停止。

10 离开 * ,5, 6, 3, 4 -> *4, 5, 6, 3 -> 左(0)或右(+1)子节点哪个更大 -> +1 更大;是否 4 大于 +1 子节点?不是,交换。

  • 6, 5, *4, 3 -> *4 没有子节点,停止。

6 离开 *,5, 4, 3 -> *3, 5, 4 -> +0 子节点更大 -> 是否 3 大于 5?不是,交换 -> 5, *3, 4,*3 没有子节点,停止。

5 离开 3, 4 -> *4, 3 -> +0 子节点最大,因为没有右子节点 -> 是否 4 大于 3?不是,退出。
4 离开 3。
3 离开 *。
按降序提取的数字:11, 10, 6, 5, 4, 3。

快速排序
一种可能的解决方案是,适配这个排序单词的快速排序方法来对整数进行排序。否则,可以尝试重新编写非通用的 qsort 函数 qsortsimppartitionswap,以对整数进行排序。

/*
 * qsortsimp.h
 *
 *  Created on: 17/03/2013
 *      Author: anonymous
 */

#ifndef QSORTSIMP_H_
#define QSORTSIMP_H_
#include <stdlib.h>
void qsortsimp( void* a, size_t elem_sz, int len, int(*cmp) (void*,void*) );
void shutdown_qsortsimp();

#endif /* QSORTSIMP_H_ */

//----------------------------------------------------------------------------

/*   qsortsimp.c
 *   author : anonymous
 */
#include "qsortsimp.h"
#include<stdlib.h>
#include<string.h>

	static void * swap_buf =0;
	static int bufsz = 0;

void swap( void* a, int i, int j, size_t elem_sz) {
	if (i==j) return;
	if (bufsz == 0 || bufsz < elem_sz) {
		swap_buf = realloc(swap_buf, elem_sz);
		bufsz = elem_sz;
	}

	memcpy(swap_buf, a + i * elem_sz, elem_sz);
	memcpy(a + i * elem_sz, a + j * elem_sz, elem_sz);
	memcpy(a + j * elem_sz, swap_buf, elem_sz);
}

void shutdown_qsortsimp() {
	if (swap_buf) {
		free(swap_buf);
	}
}

int partition(void* a, size_t elem_sz, int len, int (*cmp)(void*, void*)) {
	int i = -1;
	int j = len - 1;
	void* v = a + j * elem_sz;

	for(;;) {
		while ((*cmp)(a + (++i) * elem_sz, v) < 0);
		while ((*cmp)(v, a + (--j) * elem_sz) < 0)
			if (j == 0) break;
		if (i >= j) break;
		swap(a, i, j, elem_sz);
	}
	swap(a, i, len - 1, elem_sz);
	return i;
}

void qsortsimp(void* a, size_t elem_sz, int len, int(*cmp) (void*,void*)) {
	if (len > 2) {
		int p = partition(a, elem_sz, len, cmp);
		qsortsimp(a, elem_sz, p, cmp);
		qsortsimp(a + (p + 1) * elem_sz, elem_sz, len - p - 1, cmp);
	}
}

/*
Name        : words_quicksort.c
 Author      : anonymous
 Version     :
 Copyright   :  
 Description : quick sort the words in moby dick in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "qsortsimp.h"

void printArray(const char* a[], int n) {
	int i;
	for(i = 0; i < n; ++i) {
		if (i != 0 && i % 5 == 0) {
			printf("\n");
		}
		if (i % 1000000 == 0) {
			fprintf(stderr, "printed %d words\n", i);
		}
		printf("%s  ", a[i]);
	}
	printf("\n");
}

const int MAXCHARS = 250;
char** wordlist = 0;
int nwords = 0;
int remaining_block;
const size_t NWORDS_PER_BLOCK = 1000;

void freeMem() {
	int i = nwords;
	while (i > 0) {
		free(wordlist[--i]);
	}
	free(wordlist);
}

static char* fname = "~/Downloads/books/pg2701-moby-dick.txt";

void getWords() {
	char buffer[MAXCHARS];
	FILE* f = fopen(fname, "r");
	int state = 0;
	int ch;
	int i;
	while ((ch = fgetc(f)) != EOF) {
		if (isalnum(ch) && state == 0) {
			state = 1;
			i = 0;
			buffer[i++] = ch;
		} else if (isalnum(ch) && i < MAXCHARS - 1) {
			buffer[i++] = ch;
		} else if (state == 1) {
			state = 0;
			buffer[i++] = '\0';
			char* dynbuf = malloc(i);
			int j;
			for (j = 0; j < i; ++j) {
				dynbuf[j] = buffer[j];
			}
			i = 0;
			if (wordlist == 0) {
				wordlist = calloc(NWORDS_PER_BLOCK, sizeof(char*));
				remaining_block = NWORDS_PER_BLOCK;
			} else if (remaining_block == 0) {
				wordlist = realloc(wordlist, (NWORDS_PER_BLOCK + nwords) * sizeof(char*));
				remaining_block = NWORDS_PER_BLOCK;
				fprintf(stderr, "allocated block %d , nwords = %d\n", remaining_block, nwords);
			}
			wordlist[nwords++] = dynbuf;
			--remaining_block;
		}
	}
	fclose(f);
}

void testPrintArray() {
	int i;
	for(i = 0; i < nwords; ++i) {
		printf("%s | ", wordlist[i]);
	}
	putchar('\n');
	printf("stored %d words. \n", nwords);
}

int cmp_str_1(void* a, void* b) {
	int r = strcasecmp(*((char**)a), *((char**)b));
	return r;
}

int main(int argc, char* argv[]) {
	if (argc > 1) {
		fname = argv[1];
	}
	getWords();
	testPrintArray();

	qsortsimp(wordlist, sizeof(char*

), nwords, &cmp_str_1);

testPrintArray();

shutdown_qsortsimp();
freeMem();
puts("!!!Hello World!!!"); /* prints !!!Hello World!!! */
return EXIT_SUCCESS;

}

 
最后修改: 2025年01月12日 星期日 13:18