这是C语言的学习笔记,1万字的文章大概的讲了一下C语言中的知识点,后期会做更多的补充。
喜欢的小伙伴可以点赞支持一下,你们的点赞也是我更新的动力!
1. 什么是C语言?
C语言是一门通用计算机编程语言。例如,人与人交流用到汉语、英语、日语,人和计算机交流就用到计算机语言,计算机语言又有很多种,例如:c/c++/Java/python…
其中C语言是一种常见的计算机语言。
C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言广泛应用于底层开发。
尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的
C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台。
如图所示,最开始的计算机语言是二进制的语言,那个时候只有科学家才可以使用计算机,后来有了稍微简的汇编语言、B语言、C语言。二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSI C,作为C语言最初的标准。
目前2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)发布的C11标准是C语言的第三个官方标准,也是C语言的最新标准,该标准更好的支持了汉字函数名和汉字标识符,一定程度上实现了汉字编程。
C语言是一门面向过程的计算机编程语言,与C++,Java等面向对象的编程语言有所不同。
2 .第一个C语言程序
我们写出了一个.c的文件,通过编译器编译生成一个.exe文件。常见的编译器有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。vs2019里面集成的其实是MSVC编译器。所谓的继承开发环境就是集合编辑器、编译器、链接器、调试器于一身的开发环境。
来看看第一个代码:
#include<stdio.h>
int main()
{
//printf是一个库函数,使用得包含一个头文件
printf("hello world");
return 0;
//返回值
}
写一个代码的时候,首先要写main函数,它是程序的入口,按下F10,可以看到代码从main函数的第一行开始执行。
按下F5,可以执行代码。
一个程序只能有一个main函数,如果写了两个main函数,是不允许的,所以这次写的函数运行之后都将被注释掉。
3. 数据类型
C语言写代码是为了解决生活中各种各样的问题,例如在商场的一个商品,描述这个商品有商品名称、商品价格、商品种类等类型。C语言中也就有不同的数据类型,比如整型和浮点型:
int main()
{
//整型
int age = 20;//使用int这种类型创建了一个变量叫age,并且赋值为20
age = age + 1;
//浮点型
float pai = 3.14f;//不加f,编译器默认为double类型的数据
return 0;
}
为什么在使用计算机的时候要指定数据的类型呢?在数学中,数值的运算是绝对准确的,例如1/3的值是0.3333……(循环小数)。而在计算机中,数据时存放在存储单元中的,而且存储单元由有限个字节构成,每个存储单元存放的数据范围也是有限的,不可能存放无穷大的数,也不可能存放无限循环的小数。数据存储可以观看这一个博客:https://blog.csdn.net/weixin_43523785/article/details/122635531
每一种数据类型的大小是多少呢?
可以通过语句:printf(“%d\n”, sizeof(char));获得char类型的大小,单位为:字节(byte)。
1byte=8bit
1kb=1024byte
1MB=1024kb
1GB=1024MB
4. 变量常量
生活中的有些值是不变的(比如:圆周率,性别,身份证号码,血型等等)
有些值是可变的(比如:年龄,体重,薪资)
不变的值,C语言中用常量的概念来表示,变得值C语言中用变量来表示。
定义变量的方法
int age = 150;
float weight = 45.5f;
char ch = 'w';
变量的分类
局部变量
全局变量
#include <stdio.h>
int global = 2019;//全局变量
int main()
{
int local = 2018;//局部变量
//下面定义的global会不会有问题?
int global = 2020;//局部变量
printf("global = %d\n", global);
return 0;
}
局部变量的作用域是局部变量所在的局部范围。
当局部变量和全局变量的名字冲突的时候,局部变量优先!
变量的使用
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
这个程序可能会爆出错误,从而导致无法运行程序。原因如下:
报错中讲到的scanf_s的函数时vs编译器提供的,不是c语言自己的。而其他的编译器没有scanf_s函数,也不认识这个函数。在其他编译器中使用scanf_s,它不认识,降低了代码的跨平台性
建议写的时候尽量使用标准c提供的功能和函数。将下列代码复制到第一行就能解决。
#define _CRT_SECURE_NO_WARNINGS 1
变量的作用域和声明周期
作用域
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用
的而限定这个名字的可用性的代码范围就是这个名字的作用域。
- 局部变量的作用域是变量所在的局部范围。
- 全局变量的作用域是整个工程。
例如b定义在{ }内部,所以他的作用域就是在括号里面。
int main()
{
int a = 10;
{
int b = 20;
printf("%d\n", b);
printf("%d\n",a);
}
printf("%d\n",b);//这里不能打印b的值,因为出了它的作用域就销毁了
printf("%d\n",a);
return 0;
}
生命周期
一个生物的生命周期就是从他的出生一直到他的死亡,变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段。
4. 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
a的生命周期就是定义开始,test函数调用结束时结束,所以每一次调用test,a的值都是1,最终结果是十个2。
void test()
{
int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
i++;
}
return 0;
}
- 全局变量的生命周期是:整个程序的生命周期。
常量
C语言中的常量和变量的定义的形式有所差异。
C语言中的常量分为以下以下几种:
字面常量
const 修饰的常变量
#define 定义的标识符常量:用一个符号名代表一个常量的,称为标识符常量。
枚举常量:生活中很多量是可以用枚举来表示的,比如性别:男、女、跨性别
enum Sex
{
MALE,
FEMALE,
SECRET
};
//括号中的MALE,FEMALE,SECRET是枚举常量
int main()
{
//字面常量演示
3.14;//字面常量
1000;//字面常量
//const 修饰的常变量
const float pai = 3.14f; //这里的pai是const修饰的常变量
pai = 5.14;//这里报错,pai是不能直接修改的!
//#define的标识符常量 演示
#define MAX 100
printf("max = %d\n", MAX);
//枚举常量演示
printf("%d\n", MALE);//0
printf("%d\n", FEMALE);//1
printf("%d\n", SECRET);//2
return 0;
}
上面例子上的 pai 被称为 const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了
变量 pai 不能直接被改变,但是 pai 本质上还是一个变量的,所以叫常变量。
5.字符串、转义字符、注释
字符串
这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串。C语言里面是没有字符串类型数据这种说法的! 用双引号括起来的就是字符串。使用%s打印字符串的内容。
注:字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。
下方的代码arr1的字符串里包含转义字符,而arr2的末尾不包含转义字符。
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a','b','c'};
printf("%d\n", strlen(arr1));//3,因为字符串的末尾隐藏了一个'\0'
printf("%d\n", strlen(arr2));//随机数,这次是15
//strlen
//string length-求字符串的长度
int len = strlen("abc");
printf("%d\n", len);
printf("%s\n", arr1);//abc
printf("%s\n", arr2);//abc烫烫烫烫蘟bc
}
转义字符
\? 在书写连续多个问号时使用,防止他们被解析成三字母词
\’ 用于表示字符常量’
\“ 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符。
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3个八进制的数字。 如: \130 X
\xdd dd表示2个十六进制数字。 如: \x30 0
int main()
{
//printf("c:\test\104\test.c");//想打印目录,但是转义字符在作怪。
printf("c:\\test\\104\\test.c\n");
//??)在有的编译器里会被理解为‘三字母词’
printf("(hehe??)\n");//在vs编译器中能够正常打印,只是说明他不解析三字母词
printf("(hehe\?\?)\n");//'\?'在书写连续多个问号时使用,防止他们被解析成三字母词
//打印引号
printf("%c\n", '\'');//单引号使用\'
printf("%c\n", '\"');//双引号使用\"
//警告字符,蜂鸣
printf("\a\a\a\a");
return 0;
}
为保证人类和设备,设备和计算机之间能进行正确的信息交换,人们编制的统一的信息交换代码,这就是ASCII码表,它的全称是“美国信息交换标准代码”,例如a的ASCII码是97(十进制),A的ASCII码是65(十进制)。
int main()
{
// \ddd 表示1~3个八进制数字
printf("%c\n", '\130');//八进制的数转为二进制对应的ascii码,88对应x
printf("%c\n",'\071');//八进制的数转为二进制对应的ascii码,57对应9
// \xdd 表示2个16进制数字
printf("%c\n", '\x30');//转为10进制为:48,对应的ascii码值为:0
printf("%c\n", '\x61');//转为10进制为:97,对应的ascii码值为:a
return 0;
}
测试:考考你识不识别转义字符。\62是一个转义字符、\t是一个转义字符,好就好在编译器将转义字符给您标注出来。
int mian()
{
printf("%d\n", strlen("c:\test\628\test.c"));//14个字符
// \t是一个字符,产生的效果是四个空格
return 0;
}
注释
- 代码中有不需要的代码可以直接删除,也可以注释掉
- 代码中有些代码比较难懂,可以加一下注释文字
注释有两种风格:
风格1: /xxxxxx/
缺陷:不能嵌套注释
风格2: //xxxxxxxx
可以注释一行也可以注释多行
6.分支语句和循环语句
具体讲解可以看我的这一篇博客:
https://blog.csdn.net/weixin_43523785/article/details/121796493
选择语句
中学的时候,班主任曾经和我们说:“在我的班上,你好好学习就可以坐在班里,你不好好学习,就滚蛋!”,这就是选择语句。
int main()
{
int input = 0;
printf("你要好好学习吗?(1/0)");
scanf_s("%d", &input);
if (input == 1)
{
printf("好\n");
}
else
{
printf("滚\n");
}
return 0;
}
循环语句
有些事必须一直做,比如大家,日复一日的学习。直到完成了自己的一个目标就可以暂时休息。
int main()
{
int line = 0;
printf("目标美团\n");
while (line<20000)
{
printf("敲代码\n");
line = line + 1;
}
if (line >= 20000)
printf("当高管\n");
else
printf("送外卖\n");
return 0;
}
7. 函数
数学中有函数,比如f(x)=2*x+1。在C语言中,可以把具有一定功能的代码封装起来,需要使用的时候再调用。函数的特点就是简化代码,使代码能够重复使用。
可以把函数想象成一个工厂,工厂的左边加入原材料,工厂的右边输出结果。
在开发时,需要调用函数,只用知道他能实现的功能,没有必要了解函数实现的内部原理。
举一个简单的加法函数。
//自定义的函数,在之前先声明
int Add(int a,int b)
{
return a + b;
}
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
scanf("%d %d", &num1, &num2);
sum = Add(num1, num2); //函数调用,使用操作符()
printf("sum=%d", sum);
return 0;
}
后面将会细讲函数,点击这里。
8. 数组
要存储1-10的数字,怎么存储?
C语言中给了数组的定义:一组相同类型元素的集合
数组的定义
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//arr就是10个元素的整型数组
char ch[5] = { 'a','b','c','d','e' };
char ch1[6] = "hello";
int arr1[10]={0};//不完全初始化
return 0;
}
数组的下标
C语言规定:数组的每个元素都有一个下标,下标是从0开始的。
数组可以通过下标来访问的。
c99标准中,支持变长数组,允许变量来指定数组的大小。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", arr[2]);//通过下标访问数组
//打印所有元素
int i = 0;
while (i <= 9)
{
printf("%d ", arr[i]);
i = i + 1;
}
return 0;
}
更多数组知识点击这里
9. 操作符
C语言包含了非常多的操作符,可以直接操作二进制位。
算数操作符 : + - * / %
int main()
{
int n = 7 / 2;
printf("%d\n", n);//3
double ret = 7.0 / 2;
printf("%lf\n", ret);
int n = 7 % 2;//取模操作符只能作用于整型
printf("%d\n", n);//1
return 0;
}
移位操作符 : >> <<
int main()
{
int a = 3;
int b = a << 1;
printf("%d", b);
return 0;
}
位操作符 :& ^ |
int main()
{
int a = 3;
int b = 5;
int c = a & b;//按位与
int d = a | b;//按位或
int e = a ^ b;//按位异或
printf("%d\n",c);//全1才为1
printf("%d\n", d);//全0才为0
printf("%d\n",e);//相同为1
return 0;
}
赋值操作符:= += -= *= /= &= ^= |= >>= <<=
单目操作符,意味着只有一个操作数。比方说a+b,这个+有a,b两个操作数,所以+为双目操作符。+a表示对a取正值,这是+就是一个单目操作符。
单目操作符 | 含义 |
---|---|
! | 逻辑反操作 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制按位取反 |
– | 前置、后置– |
++ | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
int main()
{
//单目操作符:a!
int a = 10;
printf("%d", !a);//c语言中0表示假,非0即真
//sizeof 是一个单目操作符
int a = 10;
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
printf("%d\n",sizeof(double));//8
//双目操作符:a+b,有两个操作数,所以是双目
int a = 10;
int b = -a;
printf("%d", b);
//~ 按二进制位取反
int a = -1;
//-1是负整数
//负整数的二进制有原码、反码、补码
//原码:10000000 00000000 00000000 00000001
//反码:11111111 11111111 11111111 11111110
//补码:11111111 11111111 11111111 11111111,负数以补码的形式存储
int b = ~a;
printf("%d", b);
int a = 10;
int b = ++a;//前置++,先++,后使用,b=11,a=11
int b = a++;//后置++,先试用,后++ ,b=10,a=11
printf("a=%d b=%d\n",a,b);
//逻辑与 &&
//逻辑或 ||
int a = 3;
int b = 5;
if ((a == 3) && (b == 3))
{
printf("hehe\n");
}
return 0;
}
& * . -> 这四个操作符后续会讲,已更新完毕,点击查看。
关系操作符
关系操作符 | 功能 |
---|---|
> | |
>= | |
< | |
<= | |
!= | 用于测试“不相等” |
== | 用于测试“相等” |
逻辑操作符
逻辑与 &&
逻辑或 ||
//逻辑与 &&
//逻辑或 ||
int main()
{
int a = 3;
int b = 5;
if ((a == 3) && (b == 3))
{
printf("hehe\n");
}
return 0;
}
条件操作符
exp1 ? exp2 : exp3
int main()
{
//条件操作符
//exp1 ? exp2 :exp3
//简化if语句
int a = 0;
int b = 0;
int max = 0;
scanf_s("%d %d", &a, &b);
if (a > b)
max = a;
else
max = b;
printf("%d", max);
//简化后:
max = (a > b ? a : b);
printf("%d", max);
return 0;
}
逗号表达式:exp1, exp2, exp3, …expN
逗号表达式会从左向右依次计算,整个表达式的结果是最后一个表达式的结果。
int main()
{
int a = 3;
int b = 5;
int c = 10;
int d = (a + 2, b = a - 3, c = b + 4);
printf("%d\n", d);//4
return 0;
}
下标引用,函数调用结构体成员 [] () . ->
//下标引用[]
//printf("%d\n", arr[4]);
//函数调用参考前文函数的调用()
更多讲解待更新……
10.常见关键字
简单介绍一下下:
auto 自动变量,局部变量本身就是auto的,一般会省略
break 终止,通常用在循环中 , continue 继续 do……while
case switch
char short int long float double
const 常属性:修饰变量,修饰值
default 默认
extern 用于声明外部符号
go to
signed 有符号的
unsigned 无符号的
void 无 空
volatile
struct 结构体
enum 枚举
union 联合体
typedef 类型重定义
关键字typedef
typedef 顾名思义是类型定义,这里应该理解为类型重命名。
下面的代码,相当于给unsigned int 取了一个别名u_int。
typedef unsigned int u_int;//对类型重新取一个名字
int main()
{
unsigned int num = 100;
u_int num2 = 100;//上下两个代码都是一样的
return 0;
}
关键字 static
在C语言中:
static是用来修饰变量和函数的
1. 修饰局部变量-称为静态局部变量
void test()
{
//static修饰局部变量
static int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();//每一次调test函数,使用的a都是上一次留下来的a
i++;
}
return 0;
}//结果是2~11
内存是一块比较大的存储空间,在使用的时候,内存会划分出不同的功能区域。
在学习的时候,重点关注:栈区、堆区,静态区
static修饰局部变量的时候,其实改变了变量的存储类型(栈区---->静态区)
从而使得静态区的变量出了自己的作用域也不会销毁,相当于改变了全局变量的生命周期~。
2. 修饰全局变量-称为静态全局变量
首先定义一个全局变量:创建一个新的文件,命名为add.c,里面写上
//全局变量
int g_val = 1999;
在test.c文件里写上如下代码,发现可以调用add.c里面的变量。
extern int g_val;//
int main()
{
printf("%d", g_val);
return 0;
}
如果给全局变量加上static修饰呢?就会报错。
一个全局变量在整个工程的其他文件内部被使用,是因为全局变量具有外部链接属性。当一个全局变量被static修饰的时候,这个变量的外部链接属性变成了内部链接属性(内部链接属性就是我只能自己看到,别人看不到),使得这个全局变量只能在自己所在的源文件内部使用,其他的的文件不能再使用。给我们的感觉就是作用域变小了。
3. 修饰函数-称为静态函数
add.c文件内:
int Add(int x, int y)
{
return x + y;
}
test.c文件内:
extern int Add(int x, int y);
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("%d\n", ret);
return 0;
}
这时可以调用外部函数。如果使用static修饰Add,运行时无法解析。
static修饰函数的时候,函数本身也具有外部链接属性的,被static修饰的时候,就变成了内部链接属性,这个函数只能在自己所在的源文件内部使用,不能再其他的问价内使用给我们的感觉是改变了作用域~
跟全局变量是一个相同的道理。
寄存器关键字 register
计算机的内存一般是8GB或者16GB,硬盘的内存大约500GB售价大几百,百度网盘只要手机号注册就可以获得2TB的空间,完全免费。
规律:越往上速度越快,造价越高,口空间越小。
早期的时候,计算机CPU的处理速度和内存读取的速度差不多,后来CPU的速度越来越快,内存的读取速度已经跟不上它的节奏了,即使CPU再快,电脑的速度也不能提高。就好像CPU是盖房子的,内存是搬砖的,搬砖搬得慢,盖房子盖得快,那盖房子的仍然要等待砖过来才可以继续。
寄存器就像是那个搬砖更快的工人,盖房子的可以直接从寄存器中拿到变量。
int main()
{
//register 只是建议作用,编译器字节判断
register int a = 10; //a 是寄存器变量
//寄存器变量不能取地址
return 0;
}
#define定义常量和宏
//define定义标识符常量
#define MAX 1000
//define定义宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3);
printf("sum = %d\n", sum);
return 0;
}
11. 指针
内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
指针的用法:通过指针访问所指向的对象。
int main()
{
int a = 10;//a要在内存中开辟空间
printf("%p\n",&a);
int* p = &a;//p就是一个指针变量,使用int* 修饰,int 说明p只向一个整形变量!
*p = 29;//解引用操作符
printf("%d\n", a);
return 0;
}
指针变量的大小
#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%d\n", sizeof(char *));
printf("%d\n", sizeof(short *));
printf("%d\n", sizeof(int *));
printf("%d\n", sizeof(double *));
return 0;
}
说道指针,这里直接分享我自己写的三篇博客:
C语言第六课,指针初阶
指针笔试题
C语言第十课,指针终级
12. 结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。
这里只能使用结构体来描述了。
例如:
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
结构体初始化:
//打印结构体信息
struct Stu s = {"张三", 20, "男", "20180101"};
//.为结构成员访问操作符
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);
//->操作符
struct Stu *ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps->id);
更多结构体内容,点击这里