条件判断:符合其中一个进入,如果同时符合两个条件,则只会进入第一个条件
if (条件)
{
//内容
}
else if (条件)
{
//内容
}
else
{
//内容
}
如果 switch 接收到的数字在下面的 case 里,则进入,否则进入 default 里面,注意不要忘记 break。
switch (int 类型的数字) {
case 1: //如果数字为 1 则进入 case 1:
//内容
break;
case 2: //如果数字为 2 则进入 case 2:
//内容
break;
case 3: //如果数字为 3 则进入 case 3:
//内容
break;
case 4: //如果数字为 4 则进入 case 4:
//内容
break;
default: //如果以上数字均没有 则进入 default:
//内容
break;
}
条件 ? expTrue : expFalse
如果条件成立就进入循环,不成立就进不去循环
while (条件)
{
//内容
}
判断循环在末尾,先运行一遍,如果条件成立就进入循环,不成立就进不去循环
do
{
// 内容
}
while (condition);
初始化只在遇到 for 循环市执行一次,后续循环不再执行。条件判断与 while 相同。调整是每次循环结束后,下一次条件判断之前执行。
for (初始化; 条件判断; 调整)
{
// 内容
}
ret_type fun_name (para1, *)
返回类型 函数名 函数参数(形参)
1.实参与形参
实参:
真实传给函数的参数,叫做实参,实参可以是变量,常量,表达式,函数等。无论实参是何种类型的量,在函数调用时,他们必须有确定的值,以便把这个值传给形参。
形参:
形实参数是指函数名后括号中的变量,因为形参只有在函数被调用的过程中才实例化,(分配内存单元) 形式参数在函数调用玩之后就自动销毁了,因此形式参数只在函数中有效
2.传值调用与传址调用
传值调用:
函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。
传址调用:
是把函数外部创建变量的内存地址,传递给函数参数的一种调用函数的方式,这种传入方式可以让函数和函数外部的变量建立起真正的联系,也就是说函数内部可以直接操作函数外部的变量 (传入数组时,传入的仅仅是数组第一个元素的地址,大小为4/8个字节, 地址++ == arr[i++])
strcat
char *strcat(char *dest, const char *src);
strcat:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾
原理详解:
'\0'
结束strncat
char *strncat(char *dest, const char *src, size_t n);
strncat:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止
原理详解:
'\0'
结束strchr
char *strchr(char *s, char c);
strchr:在参数 s 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的地址
原理详解:
strstr
char *strstr(char const *s1, char const *s2);
strstr:在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置
原理详解:
strcmp
int strcmp(const char *str1, const char *str2);
strcmp:把 str1 所指向的字符串和 str2 所指向的字符串进行比较
原理详解:
strncmp
int strncmp(const char *str1, const char *str2, size_t n);
strncmp:把 str1 和 str2 进行比较,最多比较前 n 个字节
原理详解:
strcpy
char *strcpy(char *dest, const char *src);
strcpy:把 src 所指向的字符串复制到 dest
原理详解:
strncpy
char *strncpy (char *dest, const char *src, size_t n);
strncpy:把 src 所指向的字符串复制到 dest,最多复制 n 个字符
原理详解:
strlen
size_t strlen(const char *str);
strlen:计算字符串的长度
原理详解:
strtok
char *strtok (char * str, const char * sep);
strtok:用于分隔字符串
str
,并删除str
中含有sep
的字符
原理详解:
sep
参数是个字符串,定义了用作分隔符的字符集合sep
字符串中一个或者多个分隔符分割的标记strtok
函数找到str
中的下一个标记,并将其用‘\0’
结尾,返回一个指向这个标记的指针。strtok
函数会改变被操作的字符串,所以在使用strtok
函数切分的字符串一般都是临时拷贝的内容并且可修改。strtok
函数的第一个参数不为 NULL
,函数将找到str中第一个标记,strtok
函数将保存它在字符串中的位置。strtok
函数的第一个参数为 NULL
,函数将在同一个字符串中被保存的位置开始,查找下一个标记。NULL
指针。memcpy
void *memcpy(void *dest, const void *src, size_t n);
memcpy:从 src 复制 count 个字节到 dest
原理详解:
memmove
void *memmove (void *destination, const void *source, size_t num);
memmove:从 src 复制 count 个字节到 dest
原理详解:
memset
void *memset(void *str, int num, size_t len);
memset:对每个字节进行初始化
原理详解:
memcmp
int memcmp(void const *s1, void const *s2, int len);
memcmp:比较从 s1 和 s2 指针开始的 num 个字节
原理详解:
递归:
内存:
type_t arr_name [const_n] = {};
C99支持-> 变长数组
int n = 10;
int arr[n];
//完全初始化
int arr1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
//不完全初始化
int arr2[10] = {0, 1, 2, 3, 4, 5};
int arr3[/*6*/] = {0, 1, 2, 3, 4, 5};
//sizeof 字节长度总合
char num0[] = {'a', 'b', 'c'};
//strlen 以'\0'为查询标志
char num1[/*4*/] = "abc";
char num2[10] = "abc";
char num3[/*4*/] = {'a', 'b', 'c', '\0'};
char num4[10] = {'a', 'b', 'c'};
int arr[3][4];//越靠左位次越高
int arr1[3][4] = {1,2,3,4,5,6,7};
//[1] [2] [3] [4]
//[5] [6] [7] [0]
//[0] [0] [0] [0]
int arr2[3][4] = {{1,2,3,4},{5,6},{7,8}};
//嵌套,将打括号所有内容当做一个元素
//[1] [2] [3] [4]
//[5] [6] [0] [0]
//[7] [8] [0] [0]
应用:
int arr[10] = {0};//结果都相等,但意义不同
printf("%p\n", arr);//arr代表首元素的地址
printf("%p\n", &arr[0]);//&arr[] == arr
printf("%p\n", &arr);//arr本身就是地址,取地址的地址就是取整个数组的地址
左移操作符(<< 左移),移动的是二进制位,先化成二进制再移位
例如1的二进制表示为0001,向左移位变成0010,最后化成十进制,用法与四则运算相同
int num1 = 1;
int num2;
num2 = num1 << 2;//num1化成二进制向左移动2位,最左边的数丢弃,最右边补一个零
printf("%d\n", num2);
num1 = num2 >> 2;//num1化成二进制向右移动2位,最右边的数丢弃,最左边补一个零
printf("%d\n\n", num1);//num2化成二进制向右移动2位
右移操作符(右移 >>),移动的是二进制位,先化成二进制再移位
int a = 4;//0 1 0 0
int b = 5;//0 1 0 1
int num1 = a & b;//取交集为 0 1 0 0
printf("4 & 5 = %d\n", num1);
int c = 3;//0 0 1 1
int d = 5;//0 1 0 1
int num2 = c & d;//取交集为 0 0 0 1
printf("3 & 5 = %d\n", num2);
int e = 4;//0 1 0 0
int f = 5;//0 1 0 1
int num3 = e | f;//取并集为 0 1 0 1
printf("4 | 5 = %d\n", num3);
int g = 3;//0 0 1 1
int h = 5;//0 1 0 1
int num4 = g | h;//取并集为 0 1 1 1
printf("3 | 5 = %d\n", num4);
int i = 4;//0 1 0 0
int j = 5;//0 1 0 1
int num5 = i ^ j;//取非交集为 0 0 0 1
printf("4 ^ 5 = %d\n", num5);
int k = 3;//0 0 1 1
int l = 5;//0 1 0 1
int num6 = k ^ l;//取非交集为 0 1 1 0
printf("3 ^ 5 = %d\n", num6);
&& 逻辑与 - 两边同时成立才成立才行
if (a == 2 && b == 3) {
printf("true\n");
} else {
printf("false\n");
}
|| 逻辑或 - 其中一个成立即成立即可
if (a == 2 || b == 3) {
printf("true\n");
} else {
printf("false\n");
}
scanf("%d", &input);
if (input)//input 为真即不等于0
{
printf("%d为真", input);
}
else if(!input)//input为假即等于0
{
printf("%d为假", input);
}
括号里的内容不参与运算,但它求的值实际是-推算-之后的值的类型和长度
int a = 5;
short b = 8;
int arr[10] = { 0 };
printf("%lu\n", sizeof(arr));
printf("%lu\n", sizeof(int [10]));
printf("%lu\n", sizeof a );
printf("%lu\n", sizeof(a));
printf("%lu\n", sizeof(int));
printf("%lu", sizeof(b = a + 2));//依然为short型,放到谁里面谁的类型说了算
printf("%d", b);//sizeof在编译期间算出2执行完毕,在运行期间将不再执行
int num = (int) 3.14;
//强制转换成int类型
指针类型决定了,指针解引用的权限有多大
//char *权限详解
int a = 0x11223344;
char *pc = &a;
*pc = 0;
printf("%x",a);//结果为11223300
//说明char *的解引用权限为1个字节
//int *权限详解
a = 0x11223344;
int *pi = &a;
*pi = 0;
printf("%x",a);//结果为0
//说明int *的解引用权限为4个字节
指针类型决定了,指针走一步,能走多远(步长)
int arr[3] = {1,2,3};
char *pc = arr; //地址+1
int *pi = arr; //地址+4
printf("%d\n", *pc); //结果为1
printf("%d\n", *(pc + 1));//结果为0
printf("%d\n", *pi); //结果为1
printf("%d\n", *(pi + 1));//结果为2
*(pc + 1) = 9;
printf("%d\n", *pc); //结果为1
printf("%d\n", *(pc + 1));//结果为9
printf("%d\n", *pi); //发生了固定变化,结果为2305
printf("%d\n", *(pi + 1));//结果为2
*(pc + 1) = 0;
printf("%d\n", *pc); //结果为1
printf("%d\n", *(pc + 1));//结果为0
printf("%d\n", *pi); //结果为1
printf("%d\n", *(pi + 1));//结果为2
野指针:
运算:
int ret = 0;
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
ret = &arr[9] - &arr[0];
printf("%d", ret);
arr[9] == 9[arr]
运算符[],满足交换律
数组运算时转化为*(arr + 9),因此 arr[9] == 9[arr]
arr[9] == *(arr + 9) == *(p + 9) == *(9 + p) == *(9 + arr) == 9[arr]
int a = 10;
int *p = &a;// *说明是指针类型,int说明指向的是int类型的量
int **pp = &p;// 第二个*说明是指针类型,int *说明指向的是int *类型的量
int ***ppp = &pp;// 第三个*说明是指针类型,int **说明指向的是int **类型的量
结构声明( structure declaration )描述了一个结构组织布局
struct PeoInfo {
char Name[8];
char Sex[4];
int Age;
char Telephone[12];
char Address[12];
};
该声明描述了一个由四个字符数组和一个整型变量组成的结构。该声明并未创建实际的数据,只是描述了该对象由什么组成。
从本质上看,结构声明创建了一个名为 struct PeoInfo 的新类型( int double 等类型等价) 。
struct:
- 它表明其后的是一个结构
- struct PeoInfo 则是这个结构的名称
结构变量定义了一个 struct PeoInfo 类型的变量
struct PeoInfo Mail;
此时此刻才向堆栈申请了一块sizeof(struct PeoInfo)
大小的空间(这里是为了便于理解所以恰好设置了40个字节的大小布局,后面会有结构体大小的计算详解)。
struct PeoInfo 类型也可以直接进行初始化
struct PeoInfo Mail = {"小明", "男", 18, "11111111111", "某小区"};
struct PeoInfo和int等其他类型一样,可以直接进行赋值,它的内容需要由花括号括起来,各初始化项由逗号进行分隔。
结构体成员的基本访问( 对象
.
访问成员 或 指针->
访问成员 )
printf("%s", Mail.Name);
printf("%s", Mail.Sex);
printf("%d", Mail.Age);
printf("%s", Mail.Telephone);
printf("%s", Mail.Address);
本质上Name、Sex、Age、Telephone、Address都是 struct PeoInfo 的下标,就像数组的下标0、1、2、3一样,数组是用[ ]
来访问所在下标位置的元素, 而结构体则是用.
和->
来访问成员。
void *malloc (size_t size);
malloc:对任意类型都可以动态内存开辟的函数
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
NULL
指针,因此 malloc 的返回值一定要做检查。void*
,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。size
为0,malloc 的行为是标准是未定义的,取决于编译器。
void *calloc (size_t num, size_t size);
calloc:对任意类型都可以动态内存开辟的函数,并且把空间的每个字节初始化为0
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针,并且把空间的每个字节初始化为0。
NULL
指针,因此 malloc 的返回值一定要做检查。void*
,所以 calloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。size
为0,calloc 的行为是标准是未定义的,取决于编译器。num
个大小为 size
的元素开辟一块空间,并且把空间的每个字节初始化为0。
void *realloc (void* ptr, size_t size);
realloc:函数就可以对动态开辟内存大小的调整
ptr
是要调整的内存地址。size
是调整后的新大小。realloc 在调整内存空间的是存在两种情况:
原有空间之后有足够大的空间:要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来 使用。这样函数返回的是一个新的内存地址。
void free (void* ptr);
free:函数用来释放动态开辟的内存
ptr
指向的空间不是动态开辟的,那 free 函数的行为是未定义的。ptr
是NULL指针,则函数什么事都不做。fopen
FILE *fopen("文件名.后缀", "打开方式");
返回一个指针,需要判断是否为空指针
fclose
int fclose(文件地址);
关闭成功返回0,否则返回EOF(-1)
FILE *P_File = fopen("文件名.后缀", "打开方式");
if (P_File == NULL) {
perror("打开文件失败:");
}
fclose(P_File);
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 出错 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
fputc
int fputc(int , FILE *stream);
向文件写入数据(从末尾开始写入)
fputc('1', P_File);
fputc('2', P_File);
fputc('3', P_File);
fgetc
int fgetc(FILE *stream);
向文件读数据(从开头开始按顺序读,读一次向后走一格)
- 读取结束返回EOF
- 正常读取返回ASCII码值
int ret = fgetc(P_File);
printf("%c", ret);
fputs
int fputs(const char *str, File *stream);
向文件写入数据(从末尾开始写入)
fputs("abcdef\n", P_File);
fputs("123456\n", P_File);
fgets
char *fgets(char *str, int n, FILE *stream)
读取数 n 包括
'/0'
的位置,所以最多读 n - 1 个
fgets(arr, 5, P_File);
printf("%s\n", arr);
fgets(arr, 20, P_File);
printf("%s\n", arr);
fprintf
int fprintf(File *stream, 后面与printf一致);
向文件写入数据(从末尾开始写入)
fprintf(P_File, "%s\n", "hello world!");
fscanf
int fscanf(File *stream, 后面与scanf一致);
读取(从开始位置)
fscanf(P_File, "%s", arr);
printf("%s\n", arr);
fwrite
int fwrite(要写入的元素的地址, 每个元素的大小, 元素的数量, File *stream);
以二进制的方式写入(字符串以二进制和文本写入结果一样)
fwrite(arr, sizeof(char), 20, P_File);
fread
int fread(要读取的元素的地址, 每个元素的大小, 元素的数量, File *stream);
返回读取到的完整元素的个数,如果读取到的个数 < 实际要读的个数,则为最后一次读取
fread(arr, sizeof(char), 20, P_File);
fseek
int fseek("文件", 偏移量, 偏移的起始位置);
确定书写的位置
- SEEK_CUR - 当前位置
- SEEK_SET - 开头位置
- SEEK_END - 结尾位置(当指向结尾时,偏移量只能为负)
fwind
int fwind("文件");
让文件指针回到起始位置
feof
int feof(文件地址);
是判断读取失败结束,还是遇到文件结尾结束
- 文本文件读取是否结束,判断返回值是否为EOF,或者是否为NULL等。每个函数有每个特定的结束标志。
- 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
符号 | 作用 |
---|---|
FILE | 进行编译的源文件 |
LINE | 文件当前的行号 |
DATE | 文件被编译的日期 |
TIME | 文件被编译的时间 |
STDC | 如果编译器遵循ANSI C,其值为1,否则未定义 |
这些预定义符号都是语言内置的
printf("file:%s line:%d\n", __FILE__, __LINE__);
#define name stuff
调用 | 作用 |
---|---|
#define MAX 1000 | 定义一个全局常量MAX,大小为1000 |
#define reg register | 为 register这个关键字,创建一个简短的名字 |
#define do_forever for( ; ; ) | 用更形象的符号来替换一种实现 |
#define CASE break; case | 在写case语句的时候自动把 break写上 |
在define定义标识符的时候不建议加上
;
#define MAX 100;
#define MIN 10;
int ret = MAX * MIN;
由于 #define 定义的内容是在预处理时将其替换到代码中,此时此刻就会出问题。因此强烈不建议在定义标识符时加上;
#define name( parament-list ) stuff
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
#define SQUARE(x) x * x
传参 | 替换 | 结果 |
---|---|---|
SQUARE( 5 ); | 5 * 5 | 25 |
SQUARE( 5 + 1 ) | 5 + 1 * 5 + 1 | 11 |
SQUARE( 5 + 2 + 1 ) | 5 + 2 + 1 * 5 + 2 + 1 | 15 |
#define SQUARE(x) (x) * (x)
传参 | 替换 | 结果 |
---|---|---|
SQUARE( 5 ); | ( 5 ) * ( 5 ) | 25 |
SQUARE( 5 + 1 ) | ( 5 + 1 ) * ( 5 + 1 ) | 36 |
SQUARE( 5 + 2 + 1 ) | ( 5 + 2 + 1 ) * ( 5 + 2 + 1 ) | 64 |
#define SQUARE(x) (x) + (x)
传参 | 替换 | 结果 |
---|---|---|
2 * SQUARE( 5 ); | 2 * ( 5 ) + ( 5 ) | 15 |
2 * SQUARE( 5 + 1 ) | 2 * ( 5 + 1 ) + ( 5 + 1 ) | 18 |
2 * SQUARE( 5 + 2 + 1 ) | 2 * ( 5 + 2 + 1 ) + ( 5 + 2 + 1 ) | 24 |
#define SQUARE(x) ((x) + (x))
传参 | 替换 | 结果 |
---|---|---|
2 * SQUARE( 5 ); | 2 * ( ( 5 ) + ( 5 ) ) | 20 |
2 * SQUARE( 5 + 1 ) | 2 * ( ( 5 + 1 ) + ( 5 + 1 ) ) | 24 |
2 * SQUARE( 5 + 2 + 1 ) | 2 * ( ( 5 + 2 + 1 ) + ( 5 + 2 + 1 ) ) | 32 |
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或 邻近操作符之间不可预料的相互作用
在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤:
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:把宏名全部大写 函数名不要全部大写
在程序中使用 #define 定义符号和宏时,需要注意:
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
宏相比函数优势的地方:
宏相比函数劣势的地方:
属 性 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏 之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使 用这个函数时,都调用那个地方的同 一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销, 所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加 上括号,否则邻近操作符的优先级可能会产生不可预料的后 果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一 次,它的结果值传递给函数。表达式 的求值结果更容易预测。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参 数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次, 结果更容易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可 以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参 数的类型不同,就需要不同的函数, 即使他们执行的任务是不同的。 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要 编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果 机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)
#include
int main() {
int array[ARRAY_SIZE];
int i = 0;
for (i = 0; i < ARRAY_SIZE; i++) {
array[i] = i;
}
for (i = 0; i < ARRAY_SIZE; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
编译指令:
gcc -D ARRAY_SIZE=10 programe.c