C语言学习笔记

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嵌套调用

在一个函数的定义中调用另一个函数的规则如下:

  1. 函数声明或定义的顺序:
  • 如果调用的函数在调用点之后定义,则需要在调用点之前声明该函数(函数原型)。
  • 如果调用的函数在调用点之前定义,则不需要额外声明。
  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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值