1.输入和输出
C语言的标准库<stdio.h>提供了输入和输出函数,常用的是格式化输入(scanf)和输出(printf),以下是常用的格式说明符,值得注意的是字符串的输出可以省略格式。
同时,由于vs2022编译器对安全函数的限制,使用printf和scanf时需要手动禁用安全函数的警告,否则将报错,即需在代码首行加上#define _CRT_SECURE_NO_WARNINGS
| 格式说明符 | 描述 | printf 示例 | scanf 示例 |
|---|---|---|---|
%d | 整数(十进制) | printf("%d", 10); | scanf("%d", &num); |
%f | 浮点数 | printf("%f", 3.14); | scanf("%f", &num); |
%lf | 双精度浮点数 | printf("%lf", 3.14); | scanf("%lf", &num); |
%c | 单个字符 | printf("%c", 'A'); | scanf("%c", &ch); |
%s | 字符串 | printf("%s", \"Hello\"); | scanf("%s", str); |
%x | 整数(十六进制) | printf("%x", 255); | scanf("%x", &num); |
%o | 整数(八进制) | printf("%o", 8); | scanf("%o", &num); |
%p | 指针地址(实现定义的格式,通常是十六进制带前缀) | printf("%p", ptr); | 读取指针极少用 scanf,如:scanf("%p", &p); |
2.基础语法
通过一段代码了解c语言的基础架构
#include <stdio.h> // 头文件包含
#define PI 3.14159 // 宏定义
// 函数声明
int add(int a, int b);
int main() { // 主函数
// 变量定义
int num1 = 0, num2 = 0, sum = 0;
// 用户输入
printf("Enter two integers: ");
scanf("%d %d", &num1, &num2);
// 函数调用
sum = add(num1, num2);
// 输出结果
printf("Sum: %d\n", sum);
return 0; // 返回 0 表示程序成功执行
}
// 函数定义
int add(int a, int b) {
return a + b;
}
3.变量与常量
变量分为局部变量(简单来说就是{}中定义的变量)和全局变量(所有文件中的{}以外的变量都为全局变量,并不是只有主文件中的才叫全局变量),变量一般要进行定义和初始化(一般情况下一并进行),局部变量只能在相应的{}中使用,全局变量可在任何文件的任何地方使用(在其他文件中使用时需要进行extern声明,如extern int i; //声明,不是定义)
定义常量一般使用#define预处理器或const定义。
4.存储类
auto
auto存储类是所有局部变量默认的存储类。定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。(这也是局部变量不能在其它部分使用的原因)
static
static存储类修饰全局变量的主要作用是限制作用域,被其修饰的全局变量只能在所在文件中使用,不能通过extern声明被其他文件使用。由于 static 限制了全局变量的作用域,仅在当前文件内可见,因此可以有效避免与其他文件中同名全局变量的命名冲突
static修饰局部变量时起到延长生命周期的作用,普通的局部变量(auto类型)在函数调用时创建,函数结束时销毁。使用 static 修饰的局部变量,其生命周期变为整个程序运行期间(类似于全局变量),且只会初始化一次(普通局部变量每次进入函数都会执行初始化语句),这样可以使得其值一直保留。值得注意的是尽管生命周期延长,但static局部变量的作用域仍是定义它的函数,即{}内部。示例如下
#include <stdio.h>
void counter() {
static int count = 0; // 静态局部变量
count++;
printf("count = %d\n", count);
}
int main() {
counter(); // 输出:count = 1
counter(); // 输出:count = 2
counter(); // 输出:count = 3
return 0;
}
register
register 存储类用于提示编译器将变量存储在寄存器中以提高访问速度,但现代编译器的优化已经使其使用场景较少。
extern
extern存储类是全局变量的默认存储类,在其它文件使用全局变量时需用extern声明。
5.函数
在调用函数之前,必须声明函数(除非函数定义出现在调用之前),所以在单文件中使用函数的一般步骤是声明函数->在主程序中调用函数->在末尾补充函数定义,这样还有一个好处是方便在一个函数的定义中调用另一个函数
#include <stdio.h>
// 函数声明
int add(int a, int b);
int main() {
int num1 = 5, num2 = 10, result
// 函数调用
result = add(num1, num2);
printf("The sum is: %d\n", result);
return 0;
}
// 函数定义
int add(int a, int b) {
return a + b;
}
5.1嵌套调用
在一个函数的定义中调用另一个函数的规则如下:
- 函数声明或定义的顺序:
- 如果调用的函数在调用点之后定义,则需要在调用点之前声明该函数(函数原型)。
- 如果调用的函数在调用点之前定义,则不需要额外声明。
- 函数的作用域:
- 函数是全局的,定义在文件中的函数可以在同一文件的任何地方调用。
- 如果函数定义在其他文件中,需要使用 extern 声明。
函数定义在调用点之前
#include <stdio.h>
// 定义函数
void greet() {
printf("Hello, World!\n");
}
void display() {
// 调用另一个函数
greet();
}
int main() {
display();
return 0;
}
函数定义在调用点之后(需要声明)
#include <stdio.h>
// 函数声明(原型)
void greet();
void display() {
// 调用另一个函数
greet();
}
// 定义函数
void greet() {
printf("Hello, World!\n");
}
int main() {
display();
return 0;
}
跨文件调用(需要 extern 声明)
如果函数定义在另一个文件中,例如 utils.c,你需要在当前文件中声明它
// utils.c
#include <stdio.h>
void greet() {
printf("Hello from utils.c!\n");
}
// main.c
#include <stdio.h>
// 声明外部函数
extern void greet();
int main() {
greet();
return 0;
}
5.2函数的链式调用
链式调用是指一个函数的返回值直接作为另一个函数的输入值,从而实现函数的连续调用。同样存在先后顺序问题,与嵌套调用的主要区别是,前者是在函数输入值部分调用其它函数,后者是在函数定义中调用其它函数。
#include <stdio.h>
// 定义两个简单的函数
int add(int x) {
return x + 2;
}
int multiply(int x) {
return x * 3;
}
int main() {
int result;
// 链式调用:将 add 的返回值作为 multiply 的输入
result = multiply(add(5));
printf("结果: %d\n", result); // 输出: 21
return 0;
}
5.3函数的递归
C 语言支持递归,即一个函数可以调用其自身。但在使用递归时,我们需要注意定义一个从函数退出的条件,否则会进入死循环。
下面一个示例介绍用函数递归生成一组斐波那契数列
#include <stdio.h>
int fibonaci(int i)
{
if(i == 0)
{
return 0;
}
if(i == 1)
{
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
int main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t\n", fibonaci(i));
}
return 0;
}
下面我们将函数的递归和字符串结合一下,这是一个简单的通过递归打印字符串的例子
#include <stdio.h>
// 递归函数:逐字符打印字符串
//我们需要补充一个知识:此处char str[]和char *str的效果是一样的,即在C语言中数组作为函数参数时,会退化为指针,也就是说str[]不仅可以用于定义数组,还可以在表达式中作为数组的首地址
void printString(char str[], int index) {
if (str[index] == '\0') { // 递归终止条件
return; // 结束递归
}
// 打印当前字符
printf("%c", str[index]);
// 递归调用
printString(str, index + 1);
}
int main() {
char str[] = "hello";
printf("字符串内容: ");
printString(str, 0); // 从索引 0 开始递归
printf("\n");
return 0;
}
6.数组
数组是C语言中用于存储一组相同类型数据的集合。数组的大小在定义时必须是固定的,且数组的索引从0开始。
一维数组
一维数组是最简单的数组形式,用于存储线性数据。
#include <stdio.h>
int main() {
// 定义并初始化一个一维数组
int arr[5] = {1, 2, 3, 4, 5};
// 访问数组元素
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
二维数组
二维数组可以看成矩阵,包含行和列。
#include <stdio.h>
int main() {
// 定义并初始化一个二维数组
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 访问二维数组元素
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]);
}
}
return 0;
}
7.enum(枚举)
枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量,它可以让数据更简洁,更易读。实际上和define起到相同的效果,用一个名字代替一个常量,增强代码可读性。
第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在下面这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
#include <stdio.h>
int main()
{
// 定义一个枚举类型 color,其中 red=1,green=2,blue=3
enum color { red=1, green, blue };
// 定义一个枚举类型变量 favorite_color
enum color favorite_color;
/* 用户输入数字来选择颜色 */
printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
scanf("%d", &favorite_color); // 使用 %d 读取整数并存储到枚举变量中
/* 输出结果 */
switch (favorite_color)
{
case red:
printf("你喜欢的颜色是红色");
break;
case green:
printf("你喜欢的颜色是绿色");
break;
case blue:
printf("你喜欢的颜色是蓝色");
break;
default:
printf("你没有选择你喜欢的颜色");
}
return 0;
}
值得注意的是在对枚举常量赋值时,不能简单地给它赋整型的值,正确赋值方法有两种,其一是直接给它赋值名字,其二是赋值整型,但是需要进行强制类型转化,转化为自己定义的枚举类型
enum color { red = 1, green, blue };
enum color favorite_color;
// 方法 1:直接使用枚举标识符赋值
favorite_color = red; // 使用枚举值 red 直接赋值
// 方法 2:通过显式类型转换赋值
favorite_color = (enum color)1; // 将整数 1 转换为枚举类型
8.指针
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("var 变量的地址: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );
/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );
return 0;
}
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。 int *ptr = NULL;
9.函数指针与回调函数
函数指针
函数指针是一个指针变量,它存储的是函数的地址。通过函数指针,我们可以调用函数,就像直接调用函数名一样。
#include <stdio.h>
// 定义一个普通函数
int add(int a, int b) {
return a + b;
}
int main() {
// 定义一个函数指针
int (*func_ptr)(int, int);
// 将函数地址赋值给函数指针
func_ptr = add;//这里也可以用&add,在C语言中,函数名本身就是一个指向该函数的指针,所以add和&add是等价的
// 使用函数指针调用函数
int result = func_ptr(3, 5);
printf("Result using function pointer: %d\n", result);
return 0;
}
回调函数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。简单讲:回调函数是由别人的函数执行时调用你实现的函数。这使得我们可以自己决定我们给一个函数传入哪个函数的地址,即将选择调用哪个函数的权利交给我们自己。
#include <stdio.h>
// 定义一个普通函数,用于打印整数
void printNumber(int num) {
printf("Number: %d\n", num);
}
// 定义一个函数,接受一个回调函数作为参数
void processNumber(int num, void (*callback)(int)) {
// 调用回调函数
callback(num);
}
int main() {
int value = 42;
// 调用 processNumber,并传入回调函数 printNumber
processNumber(value, printNumber);
return 0;
}
10.字符串
一段代码介绍字符串的用法
#include <stdio.h>
int main ()
{
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
printf("菜鸟教程: %s\n", site );
return 0;
}
//将会输出 菜鸟教程: RUNOOB
字符数组不能直接赋值字符串
在C语言中,字符数组不能直接通过赋值操作(=)将字符串常量赋值给它。必须使用字符串操作函数(如 strcpy,注意需要包含<string.h>文件)或逐字符赋值。
错误示例:
char str[10];
str = "Hello"; // 错误:字符数组不能直接赋值
正确示例:
使用 strcpy 函数将字符串复制到字符数组:
#include <string.h>
char str[10];
strcpy(str, "Hello"); // 正确:将字符串内容复制到字符数组
注意事项:
- 字符数组的大小必须足够容纳字符串内容和结束符
\0。 - 在vs2022环境下,同样需要禁用安全函数警告
#define _CRT_SECURE_NO_WARNINGS
11.结构体
结构体(struct)是C语言中一种用户自定义的数据类型,用于将不同类型的数据组合在一起。和枚举类似,但是枚举只能表示整型常量,访问方式也不同,结构体通过点运算符(.)访问成员,枚举直接使用枚举值的名称代替整型常量。
#include <stdio.h>
#include <string.h>
// 定义结构体
struct Student {
int id; // 学号
char name[50]; // 姓名
float score; // 分数
};
int main() {
// 定义结构体变量
struct Student student1;
// 给结构体成员赋值
student1.id = 1;
strcpy(student1.name, "张三");
student1.score = 95.5;
// 输出结构体成员
printf("学号: %d\n", student1.id);
printf("姓名: %s\n", student1.name);
printf("分数: %.2f\n", student1.score);
return 0;
}
除了以上对结构体逐个成员赋值的方法,我们也可以直接在定义结构体变量时对成员进行初始化。struct Student student1 = {1, "李四", 88.5};// 定义时初始化
12.typedef
typedef 是 C 语言中的关键字,用于为已有的数据类型定义一个新的名字,主要用于简化代码和提高可读性。和#define有一定区别,typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名。
示例:定义数据类型别名
#include <stdio.h>
typedef unsigned int uint; // 为 unsigned int 定义别名
int main() {
uint a = 10; // 等价于 unsigned int a = 10;
printf("a 的值是: %u\n", a);
return 0;
}
示例:简化结构体定义
#include <stdio.h>
typedef struct {
int id;
char name[50];
} Student; // 为结构体定义别名
int main() {
Student s1 = {1, "张三"};//如果不用typedef,则写成struct Student s1 = {1, "张三"};
printf("学号: %d, 姓名: %s\n", s1.id, s1.name);
return 0;
}
45

被折叠的 条评论
为什么被折叠?



