TODO: 前言 基本语句 函数 数组 字符串
# 程序结构和基本语句
# 选择结构
# if
语句
TODO
# if...else
语句
TODO
# if...elseif...else
语句
TODO
# switch
语句
TODO
# 循环结构
# while
语句
TODO
# do...while
语句
TODO
# for
语句
TODO
# 跳转语句 break
continue
goto
TODO
# 函数
# 函数的定义 / 声明
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 | |
} | |
// 返回类型 函数名 (形参列表) | |
// { | |
// | |
// } |
定义函数时指定的参数,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据。所以,形参里的变量不能赋值
定义函数指定形参时格式必须为类型 + 变量
#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
# 函数的形参和实参
形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用
实参出现在主调函数中,进入被调函数后,实参也不能使用
实参变量对形参变量的数据传递是 “值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参
在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放
实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值
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; | |
} |
- 实参可以是常量、变量或表达式,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。所以,这里的变量是在圆括号外定义好、赋好值的变量
# 数组和字符串
在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来,称为数组。数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的
- 数组属于构造数据类型
- 一个数组可以分解为多个数组元素:这些数组元素可以是基本数据类型或构造类型
int a[10]; | |
struct Stu id[10]; |
- 按数组元素类型的不同,数组可分为:数值数组、字符数组、指针数组、结构数组等
int a[10]; // 数值数组 | |
char s[10]; // 字符数组 | |
char *p[10]; // 指针数组 |
# 一维数组 a[]
# 定义
数组名字符合标识符的书写规定 (数字、英文字母、下划线)
数组名不能与其它变量名相同,同一作用域内是唯一的
方括号 [] 中常量表达式表示数组元素的个数
定义数组时 [] 内最好是常量,使用数组时 [] 内即可是常量,也可以是变量
# 初始化
- 在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为零。局部数组若不初始化,内容为随机值
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。所以说初始化为随机值是什么情况下?
#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 |
数组名是一个地址的常量,代表数组中首元素的地址
#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]
定义了一个三行四列的数组:
# 初始化
// 分段赋值 | |
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 }; |
数组名是一个地址的常量,代表数组中首元素的地址
#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 的数组
#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; | |
} |
# 初始化
#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
#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()
在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过scanf
和gets
输入一个字符串的时候,不包含结尾的 “\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