前言
在课程学习过程中,深刻感到自己C语言基础不足,因此写下这篇博客来作为复习回顾。 ==声明:==该笔记面向有基础的同学,只记录本人之前学习中遗漏或者模糊的部分。
1. C语言概述
1.1 起源和特点
贝尔实验室D.M.Ritchie在B语言基础上研发出C语言。C语言可以直接对硬件操作,具有高级语言和低级语言的特性,且效率只比汇编低10%-20%.
1.2 语言开发环境
使用Visual Studio 2022,其中需要知道,一个解决方案可以包含多个项目,每一个项目必须生成一个可执行文件。
2. 数据类型、运算符及表达式
2.1 整型和浮点型
计算机在保存不同的数据类型时所占用的内存大小是不同的。
32位系统 64位系统
char 1 1
int 4 4
double 8 8
long 4 8
long long 8 8
int类型范围是KaTeX parse error: Can't use function '\~' in math mode at position 11: -2^{32}-1 \?~? 2^32 -1,即-2147483648 ~ 2147483647
以0开头的数字在计算机中默认为八进制,以0x开头的数字默认为十六进制:
int t1, t2;
t1 = 012;
t2 = 0x121;
printf("t1的值:%d\nt2的值:%d", t1, t2);
可以用sizeof运算符来获得某变量或数据类型所占用字节数:
#include <iostream>
struct student {
int age;
int sex;
};
int main()
{
int t2;
t2 = 012;
printf("student结构体占用的字节数是:%d\nt2占用的字节数是:%d", sizeof(student),sizeof(t2));
return 0;
}
在常数后面加字母U或u,代表unsigned int 。 在常数后面加字母L或l,代表long 。 在常数后面加字母F或f,代表float 。
unsigned int t1 = 23U;
long int t2 = 189L;
float t3 = 2.13F;
int a = 10;
a = 2.13F;
printf("a的值是:%d", a);
float提供6位有效数字(考虑四舍五入) double提供15~16位有效数字 有效数字:假如原数为1234.5678,但是有效数字为1,则最后存下的数字是1000.0000;若原数字是0.12345,但是有效数字是1,则存下0.1XXXX,X表示不确定。
2.2 字符型
本质上字符型常量存到一个字符型变量是将对应的ASCII码存到该变量中:
char c1, c2;
c1 = 97;
c2 = 98;
printf("c1 = %c, c2 = %c\n", c1, c2);
printf("c1 = %d, c2 = %d", c1, c2);
字符常量’a’和字符串常量"a"的区别:
-
内存占用不同,前者占1字节,后者占2字节。 -
“a"本质上由’a’ 和 ‘\0’ 两个字符构成,其中’\0’ 是结束标志: printf("dfafdadf\0dfasdfa");
-
char a[1000] = "asdfaf\0dfef";
printf(a);
2.3 强制类型转换
语法格式:(类型名)(表达式名)
强制类型转换并不会改变原变量的数据类型,而是产生一个中间变量:
float x;
int y;
x = 1.23;
y = (int)x;
printf("x = %f, y = %d", x, y);
2.4 逗号表达式和逗号运算符
逗号运算符是优先级最低的运算符,基本语法是:逗号表达式1, 逗号表达式2 , 基本规则是先求解表达式1,再求解表达式2:
int x, a;
a = (4, 5);
a = (3 * 5, 6 + 8);
a = 3 * 5, a * 4;
3. 程序的基本结构与语句
3.1 while和do … while
int cnt1 = 5, cnt2 = 5;
while (cnt1 >= 0)
{
printf("cnt1的值是:%d\n", cnt1);
cnt1--;
}
puts("");
do
{
printf("cnt2的值是:%d\n", cnt2);
cnt2--;
} while (cnt2 >= 10);
3.2 #include <> 和 “” 的区别
#include <文件名> :Visual Studio会直接去系统目录下找该文件。 #include "文件名" :Visual Studio会先从源文件所在目录开始查找,若找不到再去系统目录寻找,因此常用""包含一些自己写的头文件。
3.3 printf函数
3.4 scanf函数
默认识别回车和空格作为输出结束。
4. 逻辑运算和判断选择
4.1 优先级
- ! > 算术运算符 > 关系运算符 > &&和 || > 赋值运算符
4.2 逻辑运算符的短路原则
当目前能够判断出整个式子真假时,依据短路原则,不会继续执行,例如a && b && c ,只要a若为假,则后续不再执行。
5. 循环控制
5.1 goto
int i = 1, sum = 0;
loop:
if (i <= 100)
{
sum += i;
i++;
goto loop;
}
printf("1+2+...+100的总和是:%d", sum);
for循环
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= i; j++)
{
int k = i * j;
printf("%d * %d = %d ", i, j, k);
}
puts("");
}
6. 数组
6.1 字符数组
转义字符\0即数字0,因此,当我们数组初始化时,如果给值个数大于数组长度,但是实际上输出时,遇见默认的0也会自动停止。
字符串常量在定义时,系统会在其末尾加上\0:
char s[100] = "I am happy";
出现烫烫烫的原因:
char s[] = { 'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y' };
printf("%s\n", s);
printf需要遇见\0停止,但是显然s没有终止符,因此会输出很多垃圾信息,直到巧合遇见了\0。
字符数组在使用scanf进行输入时,数组名前面不用加&,因为数组名本身就是地址。
6.2 strcat函数
基本语法:strcat (字符数组1, 字符数组2) ,将字符串2拼接到字符串1后面,并且自动去除\0。
注意:
-
该函数在<string.h>头文件中。 -
字符数组1的大小需要足够大。
char str1[10] = "one";
char str2[10] = "two";
printf("%s", strcat(str1, str2));
6.3 strcpy函数
基本语法:strcpy(字符数组1, 字符串2)
char s1[10] = "one1234";
char s2[10] = "cat";
printf("%s", strcpy(s1, s2));
该函数的本质是将字符串2(连同结束标志)挨个赋值到字符数组1的前几位,这样当输出时,因为结束标志的存在,只会输出字符串2。
6.4 strcmp函数
基本语法:strcmp(字符串1, 字符串2) ,使用ASCII码进行比较。
- 当字符串1 = 字符串2,则返回0。
- 当字符串1 > 字符串2,则返回正整数。
- 当字符串1 < 字符串2,则返回负整数。
char s1[10] = "hello";
char s2[15] = "hello";
if (!strcmp(s1, s2))
printf("两个字符串相等");
6.5 strlen函数
该函数的作用是统计字符串的长度, 其中不包括结束标志。
注意该函数和sizeof的区别:
char str[120] = "ope1";
printf("strlen(str)结果是:%d\n", strlen(str));
printf("sizeof(str)结果是:%d\n", sizeof(str));
7. 函数
7.1 函数概述
函数调用时传递给函数的参数称为实参,函数接收数据的参数称为形参,在函数调用时,实参的值会自动赋给形参。
为了通用性,在工程中,一般将所有自定义函数的声明写在一个.h头文件中,然后使用#include将这个头文件包含进来。
7.2 函数的嵌套调用
void qtfunc1();
void qtfunc2();
void qtfunc3();
int main()
{
qtfunc1();
return 0;
}
void qtfunc1()
{
printf("qtfunc1开始执行------\n");
qtfunc2();
printf("qtfunc1结束执行------\n");
}
void qtfunc2()
{
printf("qtfunc2开始执行------\n");
qtfunc3();
printf("qtfunc2结束执行------\n");
}
void qtfunc3()
{
printf("qtfunc3开始执行------\n");
printf("qtfunc3结束执行------\n");
}
7.3 函数的递归调用
int dg_jiecheng(int n)
{
if (n == 1)
return 1;
return dg_jiecheng(n - 1) * n;
}
7.4 局部静态变量
局部变量在程序运行结束后自动释放内存,而静态局部变量则可以保存上一次函数调用结束的值。
void functest()
{
static int c = 4;
printf("c的值是:%d\n", c);
c++;
return;
}
int main()
{
functest();
functest();
functest();
return 0;
}
8. 编译预处理
8.1 整体流程
每一个.cpp文件都会编译成为.o或者.obj(视系统而定),然后链接这些.o文件成为一个.exe文件。 其中编译过程分为预处理、编译、汇编。最常见的文件包含是将所有的头文件名包含在一个头文件中,然后再包含这个头文件。
8.2 带参数的宏定义
#define S(a, b) a*b
int main()
{
printf("%d", S(3, 2));
return 0;
}
8.3 条件编译
跨操作系统平台中,有一些代码只能在Windows下编译,有一些只能在Linux下编译,因此就需要用上条件编译。
9. 指针
9.1 地址、映射表和指针
以语句printf("i + j = %d", i + j) 为例,计算机从映射表中找到 i 所对应的地址(假设为1000),然后由于为int类型,故取出1000~1004所存的值,然后依照同样的逻辑取出 j 所对应的值, 最后执行加法。
指针本质上为一种特殊的变量,存储着另一变量的地址。间接访问就是如果要存取 i 的值,可以先找到存放 i 地址的这个内存位置,也就是指向 i 的指针变量,取出其值后访问地址。
其中需要注意:指针是地址,指针变量是存放其他变量地址的变量。
9.2 指针的定义和使用
int i = 7, j = 9;
int* mypoint1, * mypoint2;
mypoint1 = &i;
mypoint2 = &j;
int a, b;
a = 200;
b = 300;
int *p1 = &a;
int* p2 = &b;
printf("%d %d\n", *p1, *p2);
&*p1;
p2 = &*p1;
*&a;
(*p1) ++;
*p1 ++ = 5;
int a = 5, b = 8;
int* pmax = &a, * pmin = &b;
int* p;
if (a < b)
{
p = pmax;
pmax = pmin;
pmin = p;
}
printf("a = %d b = %d\n", a, b);
printf("max = %d min = %d\n", *pmax, *pmin);
9.3 指针变量作为函数参数
void swap(int* p1, int* p2)
{
int tmp;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void swap(int* p1, int* p2)
{
int *ptmp;
ptmp = p1;
p1 = p2;
p2 = ptmp;
}
9.4 指针数组和数组指针
int a[3][4];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++)
a[i][j] = 86;
int* p[4];
p[0] = &a[0][0];
p[1] = &a[0][1];
p[2] = &a[0][2];
p[3] = &a[0][3];
for (int i = 0; i < 4; i++)
printf("value = %d\n", *p[i]);
int (*p)[10];
int a[10];
for (int i = 0; i < 10; i++)
a[i] = i;
p = &a;
int* q;
q = (int*)p;
for (int i = 0; i < 10; i++)
{
printf("value = %d\n", *q);
q++;
}
9.5 字符串指针
char a[] = "I love China";
char b[100];
char* p1, * p2;
p1 = a;
p2 = b;
for (; *p1 != '\0'; p1++, p2++)
*p2 = *p1;
*p2 = '\0';
char a[100];
a = "I love China";
const char *a;
a = "I love China";
9.6 函数指针
函数代码也存储在内存中,因此可以用一个指针指向函数的起始地址,即指针。
int c;
int (*p)(int a, int b);
p = max;
c = (*p)(5, 19);
printf("c = %d\n", c);
这里需要注意,max和p这两个变量保存的地址不同,是因为Visual Studio会建立函数-地址对应表,也就是max保存的是相对地址,而p保存的是绝对地址。
9.7 指针变量补充
指针变量也可以指向NULL,NULL是一个宏,即地址为0的单元,系统保证该单元不存放任何有效的数据。
char *p;
p = NULL;
if (p == NULL) {...}
10. 结构体
基本语法:struct 结构体名 {成员列表} 变量名列表; ,使用结构体成员运算符来引用成员,如s.age
10.1 结构体指针访问成员
struct student stu;
struct student* ps;
ps = &stu;
(*ps).num = 1000;
ps->age = 1;
struct student stu[10];
struct student *ps;
ps = stu;
10.2 共用体
共用体和结构体的区别更多是在内存上,而在实际用法上和结构体相差不大,具体不进行深究,遇见实际问题再说。
union myuni
{
int carnum;
char cartype;
char name;
}a, b, c;
printf("%d\n", a.carnum);
10.3 枚举类型
enum color
{
Red,
Green,
Blue,
Yellow
}mycolor1, mycolor2;
int main()
{
mycolor1 = Yellow;
printf("%d\n", mycolor1);
return 0;
}
10.4 typede定义类型
typedef struct LNode
{
int node;
struct LNode *next;
}LNode, *LinkList;
int n[100];
int NUM[100];
typedef int NUM[100];
NUM n;
11. 位运算
11.1 简介和例子
常见位运算中& 和| 就不再赘述,主要记录一下^ 和~ :
如果想某二进制位翻转,则只需要将其与1进行异或即可;若想保持二进制不变,则将其与0异或即可。
unsigned int temp = 38 ^ 23;
printf("%u\n", temp);
unsigned int temp = 15 << 1;
printf("%u\n", temp);
unsigned int tmp = 14 >> 1;
printf("%u\n", tmp);
#define BIT(x) (1 << (x))
enum EnumTask
{
ETask1 = BIT(0);
ETask2 = BIT(1);
ETask3 = BIT(3);
ETask4 = BIT(4);
ETask5 = BIT(5);
};
unsigned int tesk = 0;
if (tesk & ETask3)
printf("任务3已经做过\n");
else
printf("任务3未做过\n");
12. 文件读写
12.1 fopen函数和fclose函数
fopen函数是将文件以某种方式打开。
FILE *fp;
fp = fopen(文件名, 文件使用方式);
使用fclose (文件指针) 将文件关闭。
12.2 fwrite函数和fread函数
fwrite函数基本语法:fwrite(buffer, size, count, fp);
struct stu
{
char name[20];
int age;
double score;
};
int main()
{
struct stu student[2];
strcpy(student[0].name, "张三");
student[0].age = 21;
student[0].score = 92.1f;
strcpy(student[1].name, "李四");
student[1].age = 31;
student[1].score = 32.4f;
FILE* fp;
fp = fopen("test.bin", "wb");
if (fp == NULL)
printf("打开文件失败\n");
else
{
int len = sizeof(struct stu);
int res = fwrite(&student, sizeof(struct stu), 2, fp);
fclose(fp);
}
return 0;
}
fread函数参数与fwrite是一直的,并且用法类似,故省略。 ad函数
fwrite函数基本语法:fwrite(buffer, size, count, fp);
struct stu
{
char name[20];
int age;
double score;
};
int main()
{
struct stu student[2];
strcpy(student[0].name, "张三");
student[0].age = 21;
student[0].score = 92.1f;
strcpy(student[1].name, "李四");
student[1].age = 31;
student[1].score = 32.4f;
FILE* fp;
fp = fopen("test.bin", "wb");
if (fp == NULL)
printf("打开文件失败\n");
else
{
int len = sizeof(struct stu);
int res = fwrite(&student, sizeof(struct stu), 2, fp);
fclose(fp);
}
return 0;
}
fread函数参数与fwrite是一直的,并且用法类似,故省略。
|