做企业网站收费多少,移动端电商网站,网站建设与管理方向,电影题材网页设计欣赏函数举例
读取字符串#xff0c;如果字符串中含有ould则输出该字符串#xff0c;否则不输出。
#include stdio.h// 函数声明
int getLine(char s[], int lim);
int strindex(char s[], char t[]);int main() {char t[] ould; // 要查找的目标子字符串…函数举例
读取字符串如果字符串中含有ould则输出该字符串否则不输出。
#include stdio.h// 函数声明
int getLine(char s[], int lim);
int strindex(char s[], char t[]);int main() {char t[] ould; // 要查找的目标子字符串char s[100]; // 存储输入行的字符数组最大长度为 100// 循环读取输入的每一行并进行处理while (getLine(s, 100) 0) { // 调用 getLine 函数获取一行输入直到输入结束或达到最大限制if (strindex(s, t) ! -1) // 调用 strindex 函数查找子字符串 ouldprintf(%s, s); // 如果找到打印当前行}
}// 函数从输入中获取一行字符并存储在 s 中最多存储 lim-1 个字符
int getLine(char s[], int lim) {int i, c;i 0;while (--lim 0 (c getchar()) ! EOF c ! \n)s[i] c; // 将读取到的字符存储在数组 s 中if (c \n)s[i] c; // 如果读取到换行符也存储在数组 s 中s[i] \0; // 添加字符串结束符return i; // 返回当前行的字符个数不包括结束符
}// 函数在字符串 s 中查找子字符串 t如果找到返回第一个匹配的位置否则返回 -1
int strindex(char s[], char t[]) {int i, j;for (i 0; s[i] ! \0; i) { // 遍历字符串 sfor (j 0; s[i j] t[j] t[j] ! \0; j); // 检查 s 中从位置 i 开始的子串是否与 t 匹配if (t[j] \0) // 如果 t 的末尾符号已达到return i; // 返回 t 在 s 中首次出现的位置}return -1; // 没有找到 t则返回 -1
}练习
编写函数strindex(s,t)返回 t 在 s 中最右边出现的位置如果没有则返回 -1。
// 函数返回 t 在 s 中最右边出现的位置如果不存在则返回 -1
int strindex(char s[], char t[]) {int i, j, index;index -1; // 初始化 index 为 -1表示未找到 t 在 s 中的位置// 外循环遍历字符串 sfor (i 0; s[i] ! \0; i) {// 内循环尝试在 s 的当前位置 i 开始查找与 t 匹配的子串for (j 0; s[i j] t[j] t[j] ! \0; j); // 仅递增 j直到找到不匹配的字符或者 t 的末尾符号// 如果 t 的末尾符号已经达到说明在 s 中找到了与 t 完全匹配的子串if (t[j] \0) {index i; // 记录当前位置 i 为找到的最右边位置}}return index; // 返回 t 在 s 中最右边出现的位置或者 -1 如果未找到
}返回值
举例 编写函数将字符串转为double类型并返回。
第一种解决方案分开处理整数部分和小数部分。
#include stdio.h
#include ctype.hdouble atof(char s[]);int main() {printf(%f\n, atof(-321.123));
}double atof(char s[]) {int i, sign;double n;double decimal;n 0;for (i 0; isspace(s[i]); i);sign (s[i] -) ? -1: 1;do {n 10 * n (s[i] - 0);} while (s[i] ! \0 s[i] ! .);if (s[i] .) {decimal 10;do {n (s[i] - 0) / decimal;decimal * 10;} while (s[i] ! \0);}return sign * n;
}第二种解决方案统一处理整数部分和小数部分。
#include stdio.h
#include ctype.h
#include stdlib.h
#include float.hdouble atof(char s[]);int main() {printf(%f\n, atof(-321.123)); // 测试将字符串 -321.123 转换为浮点数并打印结果return 0;
}double atof(char s[]) {int i, sign;double n, power;// 跳过空白字符for (i 0; isspace(s[i]); i);// 确定符号位sign (s[i] -) ? -1 : 1;if (s[i] || s[i] -) // 跳过符号位i;// 处理整数部分for (n 0.0; isdigit(s[i]); i)n 10.0 * n (s[i] - 0);// 处理小数部分if (s[i] .)i;for (power 1.0; isdigit(s[i]); i) {n 10.0 * n (s[i] - 0);power * 10.0;}return sign * n / power;
}在atof的基础上编写atoi将会变得非常简单。
int atoi(char s[]) {return (int) atof(s);
}练习
修改atof使其可以转换由科学计数法表示的浮点数。例如1.23e-3、-1.231e2
#include stdio.h
#include ctype.hdouble atof(char s[]);int main() {printf(%f\n, atof(-321.123)); // 测试普通的负浮点数printf(%f\n, atof(-3.123e2)); // 测试科学计数法-3.123 * 10^2printf(%f\n, atof(-3.123e-03)); // 测试科学计数法-3.123 * 10^-3return 0;
}/** atof: 将字符串 s 转换为相应的双精度浮点数* 参数: s - 输入的字符串可以包含整数、小数和科学计数法表示形式* 返回值: 对应的双精度浮点数*/
double atof(char s[]) {int i, sign, science_sign, e_n;double n; // 存储尾数部分double power; // 处理小数点和科学计数法中的幂次n 0;e_n 0;power 1.0; // 初始权重为1// 跳过空白字符for (i 0; isspace(s[i]); i);// 确定符号位sign (s[i] -) ? -1 : 1;if (s[i] || s[i] -)i;// 处理整数部分do {n 10 * n (s[i] - 0);} while (isdigit(s[i]));// 处理小数部分if (s[i] .) {for (; isdigit(s[i]); power * 10)n 10 * n (s[i] - 0);}n / power; // 将尾数部分除以权重得到小数部分的值// 处理科学计数法部分if (s[i] e || s[i] E) {science_sign (s[i] -) ? -1 : 1; // 确定科学计数法的指数符号if (s[i] || s[i] -) // 跳过指数部分的符号位i;// 处理指数部分do {e_n 10 * e_n (s[i] - 0);} while (isdigit(s[i]));// 计算科学计数法中的指数部分的幂次for (int j 0; j e_n; j)power * 10;}// 返回最终结果考虑科学计数法的符号影响return (science_sign -1) ? sign * n / power : sign * n * power;
}外部变量
与内部变量相对立可以简单的把外部变量理解为定义在函数外的变量它在程序运行的期间一直存在。
下面是外部变量的应用。使用逆波兰表达式编写一个计算器实现加减乘除的功能。逆波兰表达式Reverse Polish NotationRPN是一种数学表达式的写法其中操作符位于其操作数之后而不是通常的中缀表示法中间。例如表达式 3 4 在逆波兰表达式中表示为 3 4 。
示例
中缀表达式 3 4 * 5 的逆波兰表示为 3 4 5 * 。中缀表达式 (1 2) * 3 - 4 的逆波兰表示为 1 2 3 * 4 -。
#include stdio.h
#include stdlib.h#define MAXOP 100 // 操作数和运算符的最大长度
#define NUMBER 0 // 表示获取到的是一个数字int getop(char []);
void push(double);
double pop();int main() {int type;double op2;char s[MAXOP]; // 用于存储获取的操作数或运算符的数组while ((type getop(s)) ! EOF) { // 循环获取输入直到遇到文件结尾switch (type) {case NUMBER: // 如果是数字push(atof(s)); // 将字符串转换为浮点数并压入栈中break;case : // 如果是加号push(pop() pop()); // 弹出栈顶两个数相加后再压入栈中break;case -: // 如果是减号op2 pop(); // 弹出第二个操作数push(pop() - op2); // 弹出第一个操作数与第二个操作数相减后再压入栈中break;case *: // 如果是乘号push(pop() * pop()); // 弹出栈顶两个数相乘后再压入栈中break;case /: // 如果是除号op2 pop(); // 弹出除数if (op2 ! 0.0)push(pop() / op2); // 弹出被除数与除数相除后再压入栈中避免除数为零的情况elseprintf(error: zero divisor\n); // 若除数为零则报错break;case \n: // 如果是换行符表示结束一行计算printf(\t%.8g\n, pop()); // 输出栈顶的结果计算的最终结果break;default:printf(error: unknown command %s\n, s); // 若遇到未知命令则报错break;}}
}#define MAXVAL 100 // 栈的最大深度int sp 0; // 栈顶指针指向下一个空闲位置
double val[MAXVAL]; // 栈用于存储操作数和中间结果void push(double f) {if (sp MAXVAL)val[sp] f; // 将操作数压入栈顶并将栈顶指针向上移动elseprintf(error: stack full, cant push %g\n, f); // 若栈已满则报错
}double pop() {if (sp 0)return val[--sp]; // 弹出栈顶操作数并将栈顶指针向下移动else {printf(error: stack empty\n); // 若栈为空则报错return 0.0; // 返回默认值}
}#include ctype.hint getch();
void ungetch(int);int getop(char s[]) {int i, c;while((s[0] c getch()) || c \t); // 跳过空白字符s[1] \0;if (!isdigit(c) c ! .) // 若不是数字且不是小数点return c; // 直接返回运算符i 0;if (isdigit(c))while (isdigit(s[i] c getch())); // 收集整数部分if (c .)while (isdigit(s[i] c getch())); // 收集小数部分s[i] \0;if (c ! EOF)ungetch(c); // 将多读入的字符放回输入缓冲区return NUMBER; // 返回表示数字的标记
}#define BUFSIZE 100 // 输入缓冲区的大小char buf[BUFSIZE]; // 输入缓冲区
int bufp 0; // 输入缓冲区的下一个空闲位置int getch() {return (bufp 0) ? buf[--bufp] : getchar(); // 获取字符从缓冲区或标准输入中
}void ungetch(int c) {if (bufp BUFSIZE)printf(ungetch: too many characters\n); // 若缓冲区已满则报错elsebuf[bufp] c; // 将字符放回缓冲区
}
运行
1 2 - 4 5 *-9
12 12 24
12 121212error: stach empty0程序核心原理
while (next operator or operand is not end-of-file indicator)if (number)push itelse if (operator)pop operandsdo operationpush resultelse if (newline)pop and print top of stackelseerror练习
为上面的程序添加取模运算并且使其支持负数。 支持负数
int getop(char s[]) {int i, c;// 跳过空白字符while ((s[0] c getch()) || c \t);i 0;if (c - || c ) { // 处理可能的负号或正号if (isdigit(c getch())) {s[i] c; // 如果后面紧跟着数字则将符号和数字一起存入s数组} else {ungetch(c); // 如果后面不是数字则将字符放回输入缓冲区return s[0]; // 返回符号字符本身}}if (!isdigit(c) c ! .) // 如果不是数字且不是小数点则直接返回该字符return c;if (isdigit(c)) // 收集整数部分while (isdigit(s[i] c getch()));if (c .) // 收集小数部分while (isdigit(s[i] c getch()));s[i] \0; // 添加字符串结束符if (c ! EOF)ungetch(c); // 将多读入的字符放回输入缓冲区return NUMBER; // 返回表示数字的标记
}支持命令打印输出堆栈中的数据、复制复制一个堆栈顶部数据、交换交换堆栈顶部的两个数据、清理清空堆栈数据。
#include stdio.h
#include stdlib.h
#include string.h// 常量定义
#define MAXOP 100 // 操作数或操作符的最大长度
#define NUMBER 0 // 标识找到一个数
#define COMMAND C // 标识找到一个命令int getop(char []);
void push(double);
double pop();
int action(char []);// 主函数执行逆波兰计算器
int main() {int type; // 存储当前的操作符或操作数类型double op2; // 存储第二个操作数char s[MAXOP]; // 存储输入的操作数或操作符// 主循环处理输入while ((type getop(s)) ! EOF) {switch (type) {case COMMAND: // 如果是命令执行对应操作if (action(s) ! 0)printf(error: unknow command %s\n, s);break;case NUMBER: // 如果是数将其压入栈中push(atof(s));break;case : // 加法操作push(pop() pop());break;case -: // 减法操作op2 pop();push(pop() - op2);break;case *: // 乘法操作push(pop() * pop());break;case /: // 除法操作op2 pop();if (op2 ! 0.0)push(pop() / op2);elseprintf(error: zero divisor\n);break;case %: // 取模操作op2 pop();if (op2 ! 0)push((int) pop() % (int) op2);elseprintf(error: zero modulus\n);break;case \n: // 换行输出栈顶元素的值printf(\t%.8g\n, pop());break;default: // 未知操作符或操作数printf(error: unknown command %s\n, s);break;}}
}#define MAXVAL 100 // 栈的最大深度int sp 0; // 下一个空闲栈位置
double val[MAXVAL]; // 值栈// 将值压入栈中
void push(double f) {if (sp MAXVAL)val[sp] f;elseprintf(error: stack full, cant push %g\n, f);
}// 从栈中弹出并返回值
double pop() {if (sp 0)return val[--sp];else {printf(error: stack empty\n);return 0.0;}
}#include ctype.hint getch();
void ungetch(int);// 获取下一个操作数或操作符
int getop(char s[]) {int i, c;// 跳过空白字符while((s[0] c getch()) || c \t);s[1] \0;i 0;// 处理命令if (c a c z) {while (!isblank(s[i] c getch()) c ! \n);s[i] \0;if (c ! EOF c ! \n)ungetch(c);return COMMAND;}// 处理可能的负号或正号if (c - || c ) {if (isdigit(c getch())) {s[i] c;} else {ungetch(c);return s[0];}}// 处理数if (!isdigit(c) c ! .)return c;if (isdigit(c))while (isdigit(s[i] c getch()));if (c .)while (isdigit(s[i] c getch()));s[i] \0;if (c ! EOF)ungetch(c);return NUMBER;
}#define BUFSIZE 100char buf[BUFSIZE]; // 缓冲区用于ungetch
int bufp 0; // 缓冲区指针// 获取下一个输入字符
int getch() {return (bufp 0) ? buf[--bufp] : getchar();
}// 将字符放回输入中
void ungetch(int c) {if (bufp BUFSIZE)printf(ungetch: too many characters\n);elsebuf[bufp] c;
}// 定义命令字符串
#define PRINT print
#define DUPLICATE duplicate
#define SWAP swap
#define CLEAR clear// 执行命令
int action(char s[]) {int result, i;double op1, op2;result -1;if ((result strcmp(s, PRINT)) 0) {for (i 0; i sp; i)printf(\t%.8g, val[i]);printf(\n);} else if ((result strcmp(s, DUPLICATE)) 0) {op2 pop();push(op2);push(op2);} else if ((result strcmp(s, SWAP)) 0) {op2 pop();op1 pop();push(op2);push(op1);} else if ((result strcmp(s, CLEAR)) 0) {sp 0;}return result;
}范围规则
局部变量的内存分配 声明时不分配存储空间 当在函数内部声明局部变量时编译器只会记录变量的名称、类型和作用域信息并计算其在栈帧中的偏移量。此时并没有实际的内存分配。例如在函数中声明一个局部变量 int localVar; 时编译器知道 localVar 是一个 int 类型的变量但没有立即为其分配内存。 函数调用时分配存储空间 实际的内存分配发生在函数调用时。当函数被调用时函数的栈帧在栈上被分配。栈帧包含了函数的所有局部变量的存储空间。当函数执行完毕并返回时栈帧被销毁局部变量的内存空间被释放。
在C语言中全局变量在声明时确实会分配存储空间。以下是关于全局变量内存分配的详细解释
全局变量的内存分配 声明和定义的区别 声明声明告诉编译器变量的类型和名字但不分配存储空间。例如在一个头文件中使用 extern 关键字声明一个变量这仅仅是声明而不是定义extern int globalVar;定义定义实际分配存储空间。例如在一个源文件中定义一个变量int globalVar;当变量被定义时存储空间会被分配。 分配时机 全局变量在程序启动时分配存储空间即在执行main函数之前通常在程序加载到内存时由运行时系统完成。全局变量的存储空间分配在数据段data segment中包括初始化的数据段.data和未初始化的数据段.bss。
示例代码
以下是一个全局变量的定义和使用示例
#include stdio.h// 定义全局变量
int globalVar 5;void exampleFunction() {printf(Global Variable: %d\n, globalVar);
}int main() {exampleFunction();return 0;
}头文件
C语言中的头文件Header Files是以.h结尾的文件主要用于声明函数、宏、常量和数据类型以便在多个源文件中共享。
计算器程序拆分
静态变量
在C语言中static关键字用于声明静态变量。静态变量有几个重要特性和用途具体如下
局部静态变量
当在函数内部声明一个静态变量时该变量的生命周期贯穿程序的整个运行时间但它的作用域仍然是局部的。这意味着即使函数已经退出该静态变量仍然保持其值下次再次调用该函数时变量不会重新初始化。
示例
#include stdio.hvoid counter() {static int count 0; // 局部静态变量count;printf(Count: %d\n, count);
}int main() {counter(); // 输出Count: 1counter(); // 输出Count: 2counter(); // 输出Count: 3return 0;
}在这个例子中count变量在第一次调用counter函数时初始化为0但在后续调用中它保持其值而不是重新初始化。
全局静态变量
当在函数外部即在文件的全局范围内声明一个静态变量时该变量的作用域被限制在声明它的文件内部。其他文件无法访问这个变量即使使用extern关键字也不行。这在编写大型项目时非常有用可以防止命名冲突。
示例
// file1.c
static int globalVar 0; // 全局静态变量void modifyVar() {globalVar;printf(globalVar in file1: %d\n, globalVar);
}// file2.c
extern void modifyVar();int main() {modifyVar(); // 输出globalVar in file1: 1modifyVar(); // 输出globalVar in file1: 2return 0;
}在这个例子中globalVar是一个静态全局变量尽管file2.c调用了modifyVar函数但它无法直接访问或修改globalVar。
静态函数
除了静态变量函数也可以被声明为静态的。静态函数只能在声明它们的文件中可见这有助于实现文件级的封装防止命名冲突。
示例
// file1.c
static void staticFunction() {printf(This is a static function.\n);
}void publicFunction() {staticFunction();
}// file2.c
extern void publicFunction();int main() {publicFunction(); // 输出This is a static function.// staticFunction(); // 错误无法访问静态函数return 0;
}在这个例子中staticFunction只能在file1.c中被调用其他文件无法调用它。
总结
static关键字在C语言中用于控制变量和函数的可见性和生命周期
局部静态变量在函数内部声明生命周期贯穿整个程序运行但作用域局限于函数内。全局静态变量在文件的全局范围内声明作用域限于声明它的文件。静态函数只能在声明它们的文件中可见。
使用static可以提高程序的模块化和封装性减少命名冲突并且在某些情况下可以提高程序的性能。
寄存器变量
在C语言中register关键字用于声明变量为寄存器变量register variables。寄存器变量是一种提示它告诉编译器将该变量存储在CPU寄存器中以便快速访问和处理。然而需要注意几点 语法声明一个寄存器变量的语法为在变量声明前加上register关键字如下所示 register int x;编译器提示register关键字只是对编译器的提示它建议编译器将该变量存储在寄存器中但并不强制。编译器可以选择忽略这个提示特别是当寄存器变量的数量超过了可用的寄存器数量或者它不适合存储在寄存器中时。 无法取地址不能对寄存器变量使用运算符取地址因为寄存器变量本身就可能不会在内存中有确切的地址。 使用场景寄存器变量通常用于频繁访问和修改的局部变量例如在循环中的计数器或者临时变量。它们的使用可以提高程序的执行速度因为访问寄存器比访问内存要快。 效果限制现代编译器通常能够根据优化算法自动决定哪些变量适合存储在寄存器中因此register关键字的实际效果可能受限制或者无法感知到显著的性能改进。
总之尽管register关键字曾经是一种常用的优化手段但由于现代编译器的进步和优化策略它的实际效果可能不如预期。因此现代C程序员通常不会显式使用register关键字而是依赖编译器自动进行优化。
初始化
在C语言中变量的初始化是指在声明变量的同时为其赋予一个初始值。变量可以在声明时进行初始化也可以在后续的代码中进行赋值操作。这里简要介绍C语言中变量初始化的几种方式和注意事项
1. 声明时初始化
在声明变量的同时为其赋值称为声明时初始化。示例如下
int x 10; // 声明一个整型变量x并初始化为10
float y 3.14f; // 声明一个浮点型变量y并初始化为3.14
char ch A; // 声明一个字符型变量ch并初始化为字符A2. 后续赋值
变量可以在声明后的任何时候赋值使用赋值运算符将一个值赋给变量。示例如下
int x; // 声明一个整型变量x
x 20; // 给变量x赋值为203. 默认初始化
如果变量在声明时没有被显式初始化它们将会被默认初始化。默认初始化的值取决于变量的存储类型和作用域
全局变量和静态变量如果没有显式初始化将会被初始化为0。局部变量如果没有显式初始化它们将包含一个随机值未定义行为因此在使用前最好显式初始化。
示例
int global_var; // 全局变量默认初始化为0void function() {static int static_var; // 静态变量默认初始化为0int local_var; // 局部变量未定义初始化值可能包含随机值
}4. 复合类型的初始化
数组初始化
int arr[5] {1, 2, 3, 4, 5}; // 声明一个包含5个元素的整型数组并初始化5. 字符串初始化
char str1[] Hello; // 自动确定数组大小并初始化
char pattern[] { o, u, l, d, \0 };
char str2[10] Hello; // 显式指定数组大小初始化字符串注意事项
初始化与赋值的区别初始化是在声明变量时给它一个初始值赋值是在变量已经声明后修改其值。局部变量的初始化局部变量如果没有显式初始化其值是未定义的不确定的因此使用前最好显式初始化。全局变量和静态变量的初始化它们如果没有显式初始化默认会被初始化为0。
通过适当的初始化可以确保变量在使用时具有正确的初始值有助于避免潜在的错误和不确定行为。
递归
递归是指函数调用自身的过程。
示例不借助printf的情况下打印输入数字
/* printd: print n in decimal */
void printd(int n) {if (n 0) {putchar(-); // 如果n为负数输出负号n -n; // 将n变为正数}if (n / 10) // 如果n大于等于10递归调用printd函数printd(n / 10);putchar(n % 10 0); // 输出n的个位数字将数字转换为字符
}C预处理器
The C PreprocessorC预处理器是C语言编译过程中的一个重要组成部分它在实际编译之前对源代码进行处理。预处理器指令由以 # 开头的命令组成用于在编译之前执行一些文本替换和条件编译等操作。以下是C预处理器的常见用法和功能
1. 包含文件 (#include)
#include 指令用于将外部文件的内容包含到当前文件中通常用来包含标准库头文件或自定义头文件。
#include stdio.h // 包含标准输入输出库的头文件
#include myheader.h // 包含自定义头文件2. 宏定义 (#define)
#define 指令用于定义宏宏是一种简单的文本替换。宏定义通常用来定义常量、函数或代码片段。
#define PI 3.14159 // 定义常量PI
#define SQUARE(x) ((x) * (x)) // 定义宏函数计算平方
#define forever for (;;) // 无限循环
#define max(A, B) ((A) (B) ? (A) : (B)) // 函数3. 条件编译 (#if, #ifdef, #ifndef, #endif)
条件编译指令用于根据条件包含或排除代码段。常见的条件编译指令有 #if, #ifdef, #ifndef 和 #endif。
#if !defined(HDR)
#define HDR
/* contents of hdr.h go here */
#endif4. 条件语句
可以在 #if 或 #ifdef 指令中使用 #include 来条件包含文件。
#if SYSTEM SYSV
#define HDR sysv.h
#elif SYSTEM BSD
#define HDR bsd.h
#elif SYSTEM MSDOS
#define HDR msdos.h
#else
#define HDR default.h
#endif#include HDR注意事项
预处理器指令在编译前被处理不是C语言的一部分所以它们不受语法检查和类型检查的限制。使用预处理器可以增强代码的灵活性和可维护性但过度使用可能会导致代码可读性降低和调试困难。宏定义和条件编译是预处理器最常见的用途它们使得代码能够在不同平台和条件下进行编译。
综上所述C预处理器提供了许多有用的工具和技术可以在编译之前对源代码进行多种形式的处理从而使得C语言在不同场景下具有更强的适应性和灵活性。