TODO: 前言 基本语句 函数 数组 字符串

# 程序结构和基本语句

# 选择结构

# if 语句

TODO

# if...else 语句

TODO

# if...elseif...else 语句

TODO

# switch 语句

TODO

# 循环结构

# while 语句

TODO

# do...while 语句

TODO

# for 语句

TODO

# 跳转语句 break continue goto

TODO

# 函数

# 函数的定义 / 声明

c
int process(int a,int b){
    
}
int min(int a = 1,int b = 2){ //error
    
}
int max(a,b){ //error
    
}
int max(int,int){ //error
    
}
//  返回类型 函数名 (形参列表)
//	  {
//  
//  }
  • 定义函数时指定的参数,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据。所以,形参里的变量不能赋值

  • 定义函数指定形参时格式必须为类型 + 变量

c
#include <stdio.h>
int max(int x, int y); // 函数的声明,分号不能省略
//int max (int, int); // 声明函数的另一种方式
int main()
{
	int a = 10, b = 25, num_max = 0;
	num_max = max(a, b);
	printf("num_max = %d\n", num_max);
	return 0;
}
// 函数的定义
int max(int x, int y)
{
	return x > y ? x : y;
}
  • 如果使用用户自己定义的函数,而该函数与调用它的函数(即主调函数)不在同一文件中,或者函数定义的位置在主调函数之后,则必须在调用此函数之前对被调用的函数作声明
  • 函数声明是为了在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,相当于告诉编译器,函数在后面定义,以便使编译能正常进行
  • 一个函数只能被定义一次,但可以声明多次

# 函数的调用

TODO

# 函数的形参和实参

  • 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用

  • 实参出现在主调函数中,进入被调函数后,实参也不能使用

  • 实参变量对形参变量的数据传递是 “值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参

  • 在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放

  • 实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值

c
void test(int a, int b)
{
    
}
int main()
{
	int p = 10, q = 20;
	test(p, q);	// right
	test(11, 30 - 10); // right
	test(int a, int b); //error, 不应该在圆括号里定义变量
	return 0;
}
  • 实参可以是常量、变量或表达式,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。所以,这里的变量是在圆括号外定义好、赋好值的变量

# 数组和字符串

在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来,称为数组。数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的

  • 数组属于构造数据类型
  • 一个数组可以分解为多个数组元素:这些数组元素可以是基本数据类型或构造类型
c
int a[10];
struct Stu id[10];
  • 按数组元素类型的不同,数组可分为:数值数组、字符数组、指针数组、结构数组等
c
int a[10]; // 数值数组
char s[10]; // 字符数组
char *p[10]; // 指针数组

# 一维数组 a[]

# 定义

  • 数组名字符合标识符的书写规定 (数字、英文字母、下划线)

  • 数组名不能与其它变量名相同,同一作用域内是唯一的

  • 方括号 [] 中常量表达式表示数组元素的个数

  • 定义数组时 [] 内最好是常量,使用数组时 [] 内即可是常量,也可以是变量

# 初始化

  • 在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为零局部数组若不初始化,内容为随机值
c
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };// 定义一个数组,同时初始化所有成员变量
	int b[10] = { 1, 2, 3 };// 初始化前三个成员,后面所有元素都设置为 0
	int c[10] = { 0 };// 所有的成员都设置为 0
	int d[] = { 1, 2, 3, 4, 5 };//[] 中不定义元素个数,定义时必须初始化。
类别代码(未初始化的数组)
全局数组int global[3];
静态全局数组static int global[3];
局部数组int local[3];{随机,随机,随机}/{0xcccccccc,0xcccccccc,0xcccccccc}/
静态局部数组static int local[3];

注:未初始化的局部数组经测试在 VS2019、cmake 中初始化为 0xcccccccc (-858993460),gcc 中初始化为 0。所以说初始化为随机值是什么情况下?

c
#include <stdio.h>
int main() {
    int static static_array[5];
    int local_array[5];
    int i = 0;
    printf("static array vaule: ");
    for (i = 0; i < 5; i++) {
        printf("%d ", static_array[i]);
    }
    printf("\nlocal array vaule: ");
    for (i = 0; i < 5; i++) {
        printf("%d ", local_array[i]);
    }
    return 0;
}
// output:
// static array vaule: 0 0 0 0 0
// local array vaule: -858993460 -858993460 -858993460 -858993460 -858993460
// Process finished with exit code 0

数组名是一个地址的常量,代表数组中首元素的地址

c
#include <stdio.h>
int main()
{
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };// 定义一个数组,同时初始化所有成员变量
	printf("a = %p\n", a);
	printf("&a[0] = %p\n", &a[0]);
	int n = sizeof(a); // 数组占用内存的大小,10 个 int 类型,10 * 4  = 40
	int n0 = sizeof(a[0]);// 数组第 0 个元素占用内存大小,第 0 个元素为 int,4
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

# 二维数组 a[][]

# 定义

  • 命名规则同一维数组

  • 二维数组 a 是按行进行存放的,先存放 a [0] 行,再存放 a [1] 行、a [2] 行,并且每行有四个元素,也是依次存放的。

  • 二维数组在概念上是二维的:其下标在两个方向上变化,对其访问一般需要两个下标。

  • 在内存中并并存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。例如 int a[3][4] 定义了一个三行四列的数组:

a[3][4]a[0]a[1]a[2]a[0][0], a[0][1], a[0][2], a[0][3]a[1][0], a[1][1], a[1][2], a[1][3]a[2][0], a[2][1], a[2][2], a[2][3]

# 初始化

c
// 分段赋值
	int a[3][4] = 
	{ 
		{ 1, 2, 3, 4 },
		{ 5, 6, 7, 8, },
		{ 9, 10, 11, 12 }
	};
	// 连续赋值
	int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };
	// 可以只给部分元素赋初值,未初始化则为 0
	int a[3][4] = { 1, 2, 3, 4  };
	// 所有的成员都设置为 0
	int a[3][4] = { 0 };
	//[] 中不定义元素个数,定义时必须初始化
	int a[][4] = { 1, 2, 3, 4, 5, 6, 7, 8 };

数组名是一个地址的常量,代表数组中首元素的地址

c
#include <stdio.h>
int main()
{
	// 定义了一个二维数组,名字叫 a
	// 二维数组是本质上还是一维数组,此一维数组有 3 个元素
// 每个元素又是一个一维数组 int [4]
	int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };
	// 数组名为数组首元素地址,二维数组的第 0 个元素为一维数组
	// 第 0 个一维数组的数组名为 a [0]
	printf("a = %p\n", a);
	printf("a[0] = %p\n", a[0]);
	
	// 测二维数组所占内存空间,有 3 个一维数组,每个一维数组的空间为 4*4
	//sizeof(a) = 3 * 4 * 4 = 48
	printf("sizeof(a) = %d\n", sizeof(a));
	// 测第 0 个元素所占内存空间,a [0] 为第 0 个一维数组 int [4] 的数组名,4*4=16
	printf("sizeof(a[0]) = %d\n", sizeof(a[0]) );
	// 测第 0 行 0 列元素所占内存空间,第 0 行 0 列元素为一个 int 类型,4 字节
	printf("sizeof(a[0][0]) = %d\n", sizeof(a[0][0]));
	// 求二维数组行数
	printf("i = %d\n", sizeof(a) / sizeof(a[0]));
	// 求二维数组列数
	printf("j = %d\n", sizeof(a[0]) / sizeof(a[0][0]));
	// 求二维数组行 * 列总数
	printf("n = %d\n", sizeof(a) / sizeof(a[0][0]));
	return 0;
}

# 多维数组

# 定义

a[][][].....

TODO

# 字符数组(字符串)

  • C 语言中没有字符串这种数据类型,可以通过 char 的数组来替代

  • 字符串一定是一个 char 的数组,但 char 的数组未必是字符串

  • 数字 0 (和字符 \0 等价) 结尾的 char 数组就是一个字符串,但如果 char 数组没有以数字 0 结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的 char 的数组

c
#include <stdio.h>
int main()
{
	char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; // 普通字符数组
	printf("c1 = %s\n", c1); // 乱码,因为没有’\0’结束符
	// 以‘\0’(‘\0’就是数字 0) 结尾的字符数组是字符串
	char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'}; 
	printf("c2 = %s\n", c2);
	// 字符串处理以‘\0’(数字 0) 作为结束符,后面的 'h', 'l', 'l', 'e', 'o' 不会输出
	char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
	printf("c3 = %s\n", c3);
	return 0;
}

# 初始化

c
#include <stdio.h>
// C 语言没有字符串类型,通过字符数组模拟
int main()
{
	// 不指定长度,没有 0 结束符,有多少个元素就有多长
	char buf[] = { 'a', 'b', 'c' };
	printf("buf = %s\n", buf);	// 乱码
	// 指定长度,后面没有赋值的元素,自动补 0
	char buf2[100] = { 'a', 'b', 'c' };
	printf("buf2 = %s\n", buf2);
	// 所有元素赋值为 0
	char buf3[100] = { 0 };
	//char buf4 [2] = { '1', '2', '3' };// 数组越界
	char buf5[50] = { '1', 'a', 'b', '0', '7' };
	printf("buf5 = %s\n", buf5);
	char buf6[50] = { '1', 'a', 'b', 0, '7' };
	printf("buf6 = %s\n", buf6);
	char buf7[50] = { '1', 'a', 'b', '\0', '7' };
	printf("buf7 = %s\n", buf7);
	// 使用字符串初始化,编译器自动在后面补 0,常用该方法
	char buf8[] = "agjdslgjlsdjg";
	//'\0' 后面最好不要连着数字,有可能几个数字连起来刚好是一个转义字符
	//'\ddd' 八进制字义字符,'\xdd' 十六进制转移字符
	// \012 相当于 \n
	char str[] = "\012abc";
	printf("str == %s\n", str);
	return 0;
}

# 输入输出

标识符: %s

c
#include <stdio.h>
int main()
{
	char str[100];
   
	printf("input string1 : \n");
	scanf("%s", str);//scanf (“% s”,str) 默认以空格分隔
	printf("output:%s\n", str);
	return 0;
}

# 字符串相关函数

# gets 函数

  • 函数: char *gets(char *s);

  • 使用: gets(str)

  • 功能:从标准输入读入字符,并保存到 s 指定的内存空间,直到出现换行符或读到文件结尾为止

  • 参数:

    s:字符串首地址

  • 返回值:

    成功:读入的字符串

    失败:NULL

  • 注意: gets(str) 允许输入的字符串含有空格,而 scanf(“%s”,str) 不允许含有空格。且由于 scanf()gets() 无法知道字符串 s 大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界 (缓冲区溢出) 的情况 (考虑使用 gets_s)

# fgets 函数

  • 函数: char *fgets(char *s, int size, FILE *stream);

  • 使用: fgets(str, sizeof(str), stdin);

  • 功能:从 stream 指定的文件内读入字符,保存到 s 所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了 size - 1 个字符为止,最后会自动加上字符 '\0' 作为字符串结束

  • 参数:

    s:字符串首地址

    size:指定最大读取字符串的长度 (size - 1)

    stream:文件指针,如果读键盘输入的字符串,固定写为 stdin

  • 返回值:

    成功:读入的字符串

    失败:NULL

  • 注意: fgets() 在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过 scanfgets 输入一个字符串的时候,不包含结尾的 “\n”,但通过 fgets 结尾多了 “\n”。fgets () 函数是安全的,不存在缓冲区溢出的问题。

# puts 函数

  • 函数: int puts(const char *s);

  • 使用: puts("hello world"); puts(str);

  • 功能:标准设备输出 s 字符串,在输出完成后自动输出一个 '\n'

  • 参数:

    s:字符串首地址

  • 返回值:

    成功:非负数

    失败:-1

# fputs 函数

  • 函数: int fputs(const char * str, FILE * stream);

  • 使用: fputs("hello world", stdout);

  • 功能:将 str 所指定的字符串写入到 stream 指定的文件中,** 字符串结束符 '\0' 不写入文件 **

  • 参数:

    str:字符串首地址
    stream:文件指针,如果把字符串输出到屏幕,固定写为 stdout

  • 返回值:

    成功:0

    失败:-1

# strlen 函数

  • 函数: size_t strlen(const char *s);

  • 使用: int n = strlen(str);

  • 功能:计算指定指定字符串 s 的长度,不包含字符串结束符‘\0’

  • 参数:

    s:字符串首地址

  • 返回值:

    字符串 s 的长度,size_t 为 unsigned int 类型

# strcpy 函数

  • 函数: char *strcpy(char *dest, const char *src);

  • 使用: strcpy(dest, src);

  • 功能:把 src 所指向的字符串复制到 dest 所指向的空间中,'\0' 也会拷贝过去

  • 参数:

    dest:目的字符串首地址
    src:源字符首地址

  • 返回值:

    成功:返回 dest 字符串的首地址
    失败:NULL

# strncpy 函数

  • 函数: char *strncpy(char *dest, const char *src, size_t n);

  • 使用: strncpy(dest, src, 5);

  • 功能:把 src 指向字符串的前 n 个字符复制到 dest 所指向的空间中,是否拷贝结束符看指定的长度是否包含 '\0'

  • 参数:

    dest:目的字符串首地址
    src:源字符首地址
    n:指定需要拷贝字符串个数

  • 返回值:

    成功:返回 dest 字符串的首地址
    失败:NULL

# strcat 函数

  • 函数: char *strcat(char *dest, const char *src);

  • 使用: strcat(str, src);

  • 功能:将 src 字符串连接到 dest 的尾部,‘\0’也会追加过去

  • 参数:

    dest:目的字符串首地址
    src:源字符首地址

  • 返回值:

    成功:返回 dest 字符串的首地址
    失败:NULL

# strncat 函数

  • 函数: char *strncat(char *dest, const char *src, size_t n);

  • 使用: strncat(str, src, 5);

  • 功能:将 src 字符串前 n 个字符连接到 dest 的尾部,‘\0’也会追加过去

  • 参数:

    dest:目的字符串首地址
    src:源字符首地址
    n:指定需要追加字符串个数

  • 返回值:

    成功:返回 dest 字符串的首地址
    失败:NULL

# strcmp 函数

  • 函数: int strcmp(const char *s1, const char *s2);

  • 使用: strcmp(str1, str2);

  • 功能:比较 s1 和 s2 的大小,比较的是字符 ASCII 码大小

  • 参数:

    s1:字符串 1 首地址
    s2:字符串 2 首地址

  • 返回值:
    相等:0
    大于:>0
    小于:<0

# strncmp 函数

  • 函数: int strncmp(const char *s1, const char *s2, size_t n);

  • 使用: (strncmp(str1, str2, 5);

  • 功能:比较 s1 和 s2 前 n 个字符的大小,比较的是字符 ASCII 码大小

  • 参数:

    s1:字符串 1 首地址
    s2:字符串 2 首地址
    n:指定比较字符串的数量

  • 返回值:
    相等:0
    大于:>0
    小于:<0

# sprintf 函数 TODO

# sscanf 函数 TODO

# strchr 函数

  • 函数: char *strchr(const char *s, int c);

  • 使用: strchr(src, 'a');

  • 功能:在字符串 s 中查找字母 c 出现的位置

  • 参数:

    s:字符串首地址
    c:匹配字母 (字符)

  • 返回值:

    成功:返回第一次出现的 c 地址
    失败:NULL

# strstr 函数 TODO

# strtok 函数 TODO

# atoi atof atol 函数 TODO