一、C语言常见概念

1. 第一个C语言程序

#include <stdio.h>
int main()
{
    printf("hello C\n");
    
    return 0;
}

1.1 main函数(主函数)

int main()

{

}

  • 不管一个C语言程序有多少行代码,都是从main函数开始执行,也就是说,main函数是程序的入口。
  • main函数有且仅有一个,即使一个项目里有多个.c文件,但是main函数也只能有一个。

1.2 int和return 0;

int main()

{

        return 0;

}

int是C语言中的一种数据类型——整数,写在main前面表示这个函数最后要返回一个int型的值,因此main函数的最后写return 0;正好前后呼应。

1.3 printf和库函数

printf("hello C\n");

printf是一个函数,也是一个库函数,它的功能是打印括号双引号里的信息。下面解释什么是库。

1.4 库函数和头文件

#include <stdio.h> // 包含printf库函数的头文件

C语言规定了一组函数,这些函数组成了一个函数库,被称为标准库,因此这些函数也被称为库函数。一个系列的库函数会声明在一个头文件中,就是说,想使用这个函数必须写对应的头文件。

1.5 关键字

C语言中有一批保留名字的符号,被称为保留字或者关键字。

  • 关键字都是由特殊含义的;
  • 关键字不能子集创建;
  • 自己创建标识符的时候不可以和关键字相同;

C语言的32个关键字

auto break case char const continue default 
do double else enum extern float for goto 
if int long register return short
signed sizeof static struct switch 
typedef union void volatile while

1.6 字符和ASCLL编码

键盘上能敲出来很多字符,C语言中用单引号括起来,如'a', '@', '!'

对语言中的字符有一个编码——ASCLL编码

我们最好掌握住:
  1. 字符A-Z的码值65-90
  2. 字符a-z的码值97-122
  3. 数字0-9的码值48-57
  4. 换行符\n的码值10

1.7 转义字符

  • \n : 换行符
  • \b : 退格键,光标退回一个字符,但不删除字符
  • \t : 制表符,光标移到下一个水平制表位,通常是下一个4/8的倍数
  • \v : 垂直分隔符,光标移到下一个垂直制表位,通常是下一行的同一列
  • \0 : null字符,代表没有内容。
  • \ddd : d d d 表示1到3个八进制数字。如:\130 表示字符X
  • \xdd : d d 代表2个十六进制数字。如:\x30 表示字符0

1.8 注释

// 我是单行注释

/*
    我是
    多行注释

*/

注释是不会被编译的!

二、C语言数据类型和变量

1. 数据类型介绍

1.1 内置类型

  1. 字符型
char

[signed] char // 有符号

unsigned char // 无符号
  1. 整型
// 短整型
short [int]
[signed] short [int]
unsigned short [int]

// 整型
int
[signed] int
unsigned int

// 长整型
long [int]
[signed] int
unsigned long [int]

// 更长的整型
long long [int]
[signed] long long [int]
unsigned long long [int]
  1. 浮点型
float
double
long double
  1. 布尔类型(使用头文件 <stdbool.h> )
_Bool

1.2 自定义类型

  1. 数组
  2. 结构体-struct
  3. 枚举-enum
  4. 联合体-union

2. 各种数据类型的长度

2.1 sizeof操作符

// sizeof(类型)
sizeof(char) // 1个字节 == 8 bit
sizeof(int) // 4
sizeof(short) // 2
sizeof(_Bool) // 1
sizeof(long) // 4
sizeof(long long) // 8
sizeof(float) // 4
sizeof(double) // 8
sizeof(long double) // 8 

// sizeof 表达式(表达式中不计算)
int a = 10;
sizeof a;

sizeof运算符的返回值类型为size_t

3. sign和unsigned

signed和unsigned用来修饰字符型和整型

  • signed 表示一个类型带有正负号
  • unsigned 表示该类型不带有正负号,只能表示0和正整数
  • 对于int类型默认是带有正负号,所有signed一般省去不写
  • char类型默认是否带正负号由当前系统决定
  • 整数变量声明为unsigned的好处是,同样长度的内存能够表示的最大整数值增大了一倍

4. 数据类型的取值范围

  • signed char 的最大值CHAR_MAX,最小值SCHAR_MIN
  • unsigned char 的最大值UCHAR_MAX
  • short 的最大值SHRT_MAX,最小值SHRT_MIN
  • unsigned short 的最大值USHRT_MAX
  • int 的最大值INT_MAX,最小值INT_MIN
  • long 的最大值LONG_MAX,最小值LONG_MIN
  • unsigned long 的最大值ULONG_MAX
  • long long的最大值LLONG_MAX,最小值LLONG_MIN
  • unsigned long long 的最大值ULLONG_MAX

5. 变量

5.1 变量的创建

C语言中把经常变化的值称为变量,不变的值称为常量。

// 数据类型 + 变量名;
int age; // 整型变量
char ch; // 字符变量
double weight; // 浮点型变量

变量在创建的时候就给一个初始值,叫做初始化。

int age = 18;
char ch = 'a';
double weight = 45;

5.2 变量的分类

  • 全局变量

在大括号外面定义的变量就是全局变量。全局变量使用范围最广。

  • 局部变量

在大括号内部定义的变量就是局部变量,只能局部使用。

注意:当局部变量和全局变量同名的时候,局部变量优先使用。

#include <stdio.h>

int global = 2025; // 全局变量

int main()
{
    int n = 10; // 局部变量
    printf("%d\n", global); // 2025
    
    int global = 2026;
    printf("%d\n", global); // 2026

    return 0;
}

5.3 变量的存储

  1. 局部变量存放在内存的栈区
  2. 全局变量存放在内存的静态区

6. 算数操作符

在写代码的时候需要进行一些计算,C语言提供了一组操作符叫做算数操作符

  • 加减乘:+  -  *
  • 除:/    两个整数做除法得到的结果是整数部分;两个数至少有一个是浮点数结构才能得到一个浮点数
  • 求模(余):% 只能用于整数;负数取模由第一个运算数的正负号决定。

7. 赋值操作符

创建变量的时候给一个初始值叫做初始化;在变量创建后再给一个值叫做赋值。

int a = 0; // 初始化
a = 10; // 赋值

7.1 连续赋值

从右向左一次赋值(但是还是建议拆开来写)

int a = 1;
int b = 2;
int c = b = a + 3; // 先b = a + 3; 再 c = b

7.2 复合赋值符

对一个数自增、自减

int a = 0;
a += 1; // 相当于a = a + 1;
a -= 1; // 相当于a = a - 1;

a *= 1;
a /= 1;
a %= 1;

7.3 单目操作符

前面讲到的有两个操作数的都是双目操作符。C语言中还有一些操作符是只有一个操作数的被称为单目操作符。

++
int a = 0;
printf("%d\n", a); // 0
int b = ++a; // +号在前面,先加1再赋值。相当于 a = a + 1; b = a;
printf("%d\n", a); // 1
printf("%d\n", b); // 1
int c = a++; // +号在后面,先赋值再加1。相当于 c = a; a = a + 1;
printf("%d\n", a); // 2
printf("%d\n", c); // 1
--

类比++可知

int a = 10;
int b = --a; // b = 9; a = 9;
int c = a--; // c = 9; a = 8;

8. 强制类型转换

int a = 3.14; // 这里3.14是double类型,前面写int系统会报错

// 为了消除警告,可以使用下面的强制类型转换
int a = (int)3.14;

9. scanf和printf介绍

#include <stdio.h>
int main()
{
    int n;
    scanf("%d", &n); // scanf用于输入,注意格式
    printf("%d\n", n); // printf用于输出,注意格式
}

9.1 scanf占位符

  • %c:char
  • %d:int
  • %f: float
  • %lf: double
  • %Lf: long double
  • %s:字符串
  • %[]:在方括号中指定一组匹配的字符,遇到不在集合之中的字符,匹配会停止

9.2 printf占位符

  • %c : char
  • %d :int 
  • %u : unsigned int
  • %f : float
  • %lf : double
  • %Lf : long double
  • %e (%E): 使用科学计数法的浮点数,指数部分的e(E)为小(大)写
  • %ld : long int
  • %lld:long long int
  • %s : 字符串
  • %zd : size_t类型
  • %% : 输出一个百分号

9.3 scanf输入格式

int i, j;
float x, y;
scanf("%d%d%f%f", &i, &j, &x, &y);
  1. 变量之前必须加上&运算符(指针变量除外),因为scanf传递的不是值,而是地址,&是取地址符
  2. 除了%c之外,都会自动忽略起首的空白字符;如果要强制跳过字符前的空白字符,可以写成scanf(" %c", &ch); 可以跳过0个或多个空白字符
  3. scanf不解读空格和回车,当键入回车或换行后开始读取下一个输入值
  4. %s从当前第一个非空白字符开始读起,直到遇到空白字符(空格、换行符、制表符等)为止
  5. %s在读入字符数组时不会检测字符串是否超过了数组长度,所以建议指定字符串的最长长度,超过的自动丢弃。例如
    char name[11];
    scanf("%10s", name);

    这里没有写成&name是因为数组名代表地址

  6. scanf返回一个整数,即成功读取的变量个数,如果没有读取任何项或者读取失败返回0;如果发生了读取错误或者遇到读取到文件结尾,返回常量EOF(-1)

9.4 scanf赋值忽略符

#include <stdio.h>
int main()
{
    int year = 0;
    int month = 0;
    int day = 0;
    scanf("%d-%d-%d", &year, &month, &day);
    printf("%d %d %d\n", year, month, day);

    return 0;
}

这里如果用户输入2025/07/27scanf数据解析就是失败。下面给一个解决方法

#include <stdio.h>
int main()
{
    int year = 0;
    int month = 0;
    int day = 0;
    scanf("%d%*c%d%*c%d", &year, &month, &day);

    return 0;
}

值需要把*加在任何占位符的百分号的后面,该占位符就不会返回值,解析后将被丢弃。

9.5 printf输出格式

1. 限定宽度
printf("%5d\n", 123); // %5d表示这个占位符至少为5位,不满五位前面自动添加空格(默认右对齐)
printf("%-5d\n", 123); // 添加-号左对齐
printf("12f\n", 123.45); // 由于小数精度是小数点后六位,因此,输出时前面有两个空格(小数点也占一个宽度)
2. 显示正负号
// %+d 可以保证输出的数字总带有正负号
printf("%+d\n", 123); // 输出+123
printf("%+d\n", -123); // 输出-123
3. 限定小数数位
printf("%.2f", 0.5); // 保留两位小数,输出0.50
printf("%6.2f\n", 0.5); // 最小宽度为6,保留两位小数
printf("%*.*f\n", 6, 2, 0.5); // 两个*号通过6和2传入
4. 输出部分字符串
printf("%.5s\n", "hello world"); // 只输出前5和字符即"hello"

三、分支和循环

1. if-else语句

1.1 双分支

#include <stdio.h>
int main()
{
    int n;
    scanf("%d", &n);

    if(n >= 0) // ( )里写条件句
        printf("自然数\n"); // 满足条件就执行
    else // 不满足上面if里的条件,就执行else
        printf("负数\n");
    return 0;
}

1.2 多分支

#include <stdio.h>

int main()
{
    int n;
    scanf("%d", &n);
    
    if(n > 0)
        printf("正数\n");
    else if(n < 0)
        printf("负数\n");
    else
        printf("0\n");

    return 0;
}

1.3 嵌套分支

#include <stdio.h>

int main()
{
    int n;
    scanf("%d", &n);
    
    if(n >= 0)
    {
        if(n == 0)
            printf("0\n");
        else
            printf("正数\n");
    }
    else
        printf("负数\n");
    
    return 0;
}

注意:如果有多个if-else, else总跟最接近的if匹配

2. 关系操作符

  • > 大于
  • < 小于
  • >= 大于等于
  • <= 小于等于
  • != 不等于
  • == 相等

3. 条件操作符(三目操作符)

// exp1成立就执行exp2, 否则执行exp3
exp1 ? exp2 : exp3;

4. 逻辑操作符

  • ! : 取反
  • && : 并且(两边都为真表达式才为真)
  • || : 或(两边有一个为真表达式就为真)

5. switch语句

switch(expression整型表达式)
{
    case v1 (整型常量表达式): statement(包含break;)
    case v2 : statement(包含break;)
    ...
    default : statement(包含break;)
}

6. while语句

while(表达式) // 表达式为真就执行下面的语句
{
    语句;
}

7. for循环

// for(表达式1;表达式2;表达式3){语句}

for(int i = 0; i < = 10; ++i)
{
    printf("%d\n", i);
}

8. do-while循环

do
{
    语句;
}while(表达式);
// 当符合while表达式循环停止

9. break和continue

  • break是退出当前循环,并且跳过往后的所有循环(从循环程序跳出来)
  • continue是退出当前循环,接着运行下一次循环

四、数组

1. 数组的概念

数组是一组相同类型元素的集合

  • 数组中存放一个或多个数据,但是数组元素个数不能为0
  • 数组中存放的多个数据,类型是相同的

2. 一维数组

2.1 创建和初始化

// 创建:数据类型 数组名[数组大小]
int arr[10];
char ch[100];

// 完全初始化
int arr1[3] = {1, 2, 3};
// 完全初始化可以不写数组大小
int arr2[] = {1, 2, 3, 4, 5};

// 不完全初始化
int arr3[6] = {1}; // 第一个元素为1,剩余元素默认初始化为0

#include <stdio.h>
int main()
{
    int arr[5];
    // 数组的输入
    for(int i = 0; i < 5; i++)
    {
        scanf("%d", &a[i]);
    }
    return 0;
}

2.2 数组访问

数组是通过下标引用的,数组的下标从0开始

#include <stdio.h>
int main()
{
    int arr[3] = {1, 2, 3};
    printf("%d\n", a[0]); // 1
    printf("%d\n", a[1]); // 2
    printf("%d\n", a[2]); // 3
    return 0;
}

2.3 sizeof计算数组中元素个数

#include <stido.h>
int main()
{
    int arr[10] = {0};
    printf("%d\n", sizeof(arr)); // 40(字节)
    
    int sz = sizeof(arr) / sizeof(arr[0]);

    printf("%d\n", sz); // 10(表示数组有十个元素)
    return 0;
}

3. 二维数组

3.1 二维数组的创建和初始化

// 完全初始化
int arr[3][5] = {1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15}; 
// 可看成3行5列初始化
int arr1[3][5] = {{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};
// 初始化时可以省略行,但不能省略列
int arr2[][5] = {1, 2, 3};

// 不完全初始化
double data[2][8] = {1};// 第一个元素为1,其余元素默认初始化为0

3.2 二维数组的使用

#include <stdio.h>
int main()
{
    int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
    printf("%d\n", arr[2][4]); // 6
    return 0;
}

3.3 二维数组的输入输出

#include <stdio.h>
int main()
{
    int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
    int i = 0;//遍历⾏
    //输⼊
    for(i=0; i<3; i++) //产⽣⾏号
    {
        int j = 0;
        for(j=0; j<5; j++) //产⽣列号
        {
            scanf("%d", &arr[i][j]); //输⼊数据
        }
    }
    //输出
    for(i=0; i<3; i++) //产⽣⾏号
    {
        int j = 0;
        for(j=0; j<5; j++) //产⽣列号
        {
            printf("%d ", arr[i][j]); //输出数据
        }
        printf("\n");
    }
    return 0;
}

4. C99中的变长数组

允许使用变量指定数组大小

int n = a + b;
int arr[n];

例如:

#include <stdio.h>
int main()
{
    int n = 0;
    scanf("%d", &n);//根据输⼊数值确定数组的⼤⼩
    int arr[n];
    int i = 0;
    for (i = 0; i < n; i++)
    {
        scanf("%d", &arr[i]);
    }
    for (i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

五、函数

C语言中的函数就是3完成某项特定的任务的一小段代码

1. 库函数

库函数的使用和查找

C/C++官⽅的链接:https://zh.cppreference.com/w/c/header

cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/

2. 自定义函数

ret_type fun_name(形式参数(形参))
{
    
}

// 举例

int add(int a, int b)
{
    return a + b;
}

int main()
{
    int x, y;
    cin >> x >> y;
    cout << add(x, y) << endl; // 这里传入的x和y为实际参数(实参)
}

3. return 语句

  1. return 后面可以是数值,亦可以是表达式。如果是表达式,先执行表达式,再返回表达式的结果
  2. return 后面也可以什么都没有,直接写return; 这种情况适合函数的返回类型是void的情况
  3. return语句执行后,函数就彻底返回,后面的代码不再执行
  4. return返回的值和函数返回类型不一样,系统会自动将返回的值隐式转换为函数的返回类型
  5. 如果函数中存在if等分支语句,则要保证每一种情况都要有return返回,否则就会出现编译错误。
  6. 函数返回的类型如果不写,编译器就会默认函数的返回类型是int
  7. 函数写了返回了类型,但是函数中没有使用return 返回值,那么函数的返回值是未知的。

4. 数组做函数参数

  1. 函数和形参和实参个数要匹配
  2. 函数的实参是数组,形参也是可以写成数组形式
  3. 形参如果是一维数组,数组大小可以不写
  4. 形参如果是二维数组,行可以省略,但列不能省略
  5. 数组传参,形参是不会创建新的数组的
  6. 形参操作的数组和实参的数组是同一个数组
#include <stdio.h>
void set_arr(int arr[], int sz)
{
    for(int i = 0; i < sz; ++i)
        arr[i] = -1;
}

void printf_arr(int arr[], int sz)
{
    for(int i = 0; i < sz; ++i)
        printf("%d ", arr[i]);
    printf("\n");
}

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int sz = sizeof(arr) / sizeof(arr[0]);
    set_arr(arr, sz); // 将数组内容都写成-1
    printf_arr(arr, sz); // 打印数组元素
    return 0;
}

5. 嵌套调用

嵌套调用就是函数之间的互相调用

6. 链式访问

链式访问就是将一个函数的返回值作为另一个函数的参数

7. 函数的定义和调用

7.1 单个文件

#include <stido.h>
//函数的定义:判断⼀年是不是闰年
int is_leap_year(int y)
{
    if(((y%4==0)&&(y%100!=0)) || (y%400==0))
        return 1;
    else
        return 0;
}
int main()
{
    int y = 0;
    scanf("%d", &y);
    // 函数的调用:
    int r = is_leap_year(y);
    if(r == 1)
        printf("闰年\n");
    else
        printf("⾮闰年\n");
    return 0;
}

7.2 多个文件

一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现放在源文件(.c)中。

add.c文件中

//函数的定义
int Add(int x, int y)
{
    return x+y;
}

add.h文件中

//函数的声明
int Add(int x, int y);

test.c文件中

#include <stdio.h>
#include "add.h"
int main()
{
    int a = 10;
    int b = 20;
    //函数调⽤
    int c = Add(a, b);
    printf("%d\n", c);
    return 0;
}

8. static

static是静态的意思

作用:

  1. 修饰局部变量
  2. 修饰全局变量
  3. 修饰函数

8.1 修饰局部变量

代码1:
#include <stdio.h>
void test()
{
    int i = 0;
    i++;
    printf("%d ", i);
}
int main()
{
    int i = 0;
    for(i=0; i<5; i++)
    {
        test();
    }
    return 0;
}

这里的局部变量i每次进入test函数都会先创建变量并赋值为0,出函数的时候会释放内存

代码2:
#include <stdio.h>
void test()
{
    //static修饰局部变量
    static int i = 0;
    i++;
    printf("%d ", i);
}
int main()
{
    int i = 0;
    for(i=0; i<5; i++)
    {
        test();
    }
    return 0;
}

代码2的i有累加作用,在test函数中创建好后,出函数的时候不会销毁,重新进入函数的时候接着上次累计的数值继续计算

总结:static修饰局部变量改变了变量的生命周期,本质上是改变了变量的存储类型。本来存储到栈区,被static修饰后存储到了静态区。

8.2 修饰全局变量

代码1:

add.c

int g_val = 2025;

test.c

#include <stdio.h>
extern int g_val; // extern用来声明外部符号
int main()
{
    printf("%d\n", g_val);
    return 0;
}
代码2:

add.c

static int g_val = 2025;

test.c

#include <stdio.h>
int main()
{
    printf("%d\n", g_val);
    return 0;
}

extern是用来声明外部符号的,如果一个全局的符号在A文件中定义,在B文件中想使用,就可以使用extern进行声明,然后使用。

代码1正常。代码2在编译的时候会出现链接性错误。

8.3 static修饰函数

代码1:

add.c

int Add(int x, int y)
{
    return x + y;
}

test.c

#include <stdio.h>
extern int Add(int x, int y);
int main()
{
    printf("%d\n", Add(2, 3));
    return 0;
}

代码2:

add.c

static int Add(int x, int y)
{
    return x+y;
}

test.c

#include <stdio.h>
extern int Add(int x, int y);
int main()
{
    printf("%d\n", Add(2, 3));
    return 0;
}

代码1可以正常运行,代码2就出现了链接错误。

static修饰函数和修饰全局变量是一模一样的。

本质是因为函数默认是具有外部链接属性,但是被static修饰后变成了内部链接属性,使得函数只能在自己所在的源文件内部使用。

更多推荐