C编程
C 编程
#include<stdio.h>
#define a5+2
int main() {
int ans;
ans = a * a * a;
printf("%d", c);
return 0;
}
初学练习 - 解决方案
变量命名
- 举一个不合法的 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 字节
我们不能使用 int
或 float
作为变量名。
赋值
标准的赋值方式是:
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;
}
循环示例
- 打印三角形的星号
#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;
}
- 打印倒三角形的星号
#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;
}
- 通过数学运算实现形状打印
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");
}
}
- 打印上升的菱形
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'); // 换行
}
}
解释
-
gets_s
和slen
示例:该代码要求用户输入一个句子并将其逆序输出。gets_s
是 C11 标准中用于安全获取字符串的函数,避免了gets
的潜在危险。slen
函数是用来计算字符串长度的自定义函数,它不依赖于标准库的strlen
函数。 -
打印三角形和倒三角形:这些示例使用了循环结构来打印不同形状的星号。外层循环负责控制行数,内层循环控制每行打印的星号数量。
-
数学运算形状打印:通过数学公式和循环,打印出菱形和倒菱形的形状。这些代码示例展示了如何使用
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
函数 qsortsimp
、partition
和 swap
,以对整数进行排序。
/*
* 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;
}