C语言的由来?
C语言的由来可以追溯到20世纪70年代。以下是一些关键的历史背景:
-
贝尔实验室:C语言由Dennis Ritchie和Brian Kernighan在贝尔实验室开发,最初是为了改进B语言(另一种编程语言),而B语言又是基于BCPL(Basic Combined Programming Language)。
-
UNIX系统:C语言的设计与UNIX操作系统的开发密切相关。最初,UNIX是用汇编语言编写的,但随着需求的增加,开发者们希望有一种更高效、可移植的语言来重写系统。
-
简洁和高效:C语言以其简洁性和高效性著称,能够提供底层硬件的控制,同时保持相对较高的可读性。这使得它在系统编程、嵌入式系统以及应用程序开发中得到了广泛应用。
-
标准化:1978年,C语言首次被正式描述在《The C Programming Language》一书中,书的作者是Brian Kernighan和Dennis Ritchie。1989年,C语言的标准化工作由国际标准化组织(ISO)进行,形成了ANSI C标准(C89)。
-
影响:C语言的设计理念和语法对后来的编程语言(如C++、Java、C#等)产生了深远影响。由于其强大的功能和灵活性,C语言至今仍然是计算机科学和工程领域中重要的编程语言。
这些历史背景使得C语言不仅在技术上取得了成功,也在学术界和工业界中得到了广泛的应用。
一、C语言基本框架
- C语言文件后缀:.c .C .cc
分析以下代码:
- 头文件
(1) 头文件的格式
#include
(2) 头文件是干什么的?
头文件里面存放着一些函数的声明、宏定义等
C语言规定在调用函数时,必须要在函数调用之前写上函数的声明,否则编译器会不认识该函数会报错或者警告。而且由于函数的声明在头文件中,所以要想使用指定的函数,就必须要写头文件。
(3) 我们能正常使用printf()函数,而且只写了stdio.h这一个头文件,也就意味着printf()这个函数在stdio.h这个头文件中能找到。
问题1: 头文件在哪里?
linux下的头文件都是存放在/usr/include目录下。
stdio.h也是在此目录下。
问题2:如何在Linux下,打开文件?
gedit stdio.h -> 使用gedit工具打开当前目录下的stdio.h文件。(vm可以使用)
vim stdio.h -> 使用vi/vim工具打开当前目录下的stdio.h文件。(都可)
问题3:printf()函数的声明是哪个?
extern int printf (const char *__restrict __format, ...); --> 函数声明
问题4:怎么知道函数的头文件是哪个?
使用man手册去查询(man手册是我们Linux系统提供给用户的一种学习手册)
使用方法:假设我现在需要知道scanf()这个函数的头文件是哪个
1. 在Ubuntu的终端输入命令: man -k scanf
2. 找到我们需要的scanf信息 scanf (3) - input format conversion
在Ubuntu的终端输入命令: man 3 scanf
3. 即可获取我们需要加入的头文件
4. 按q退出man手册
- main函数
(1) main函数的组成
(3) main函数一定需要参数吗?
不是一定需要参数的
当我们不需要参数时,main也可以写成:
int main() {} or int main(void) {}
3. 注释
(1) 注释的作用
有一些难懂,或者需要解析的时候,可以在代码后面加一些文字来描述,但又不能影响原本的语句执行,就出现了注释
-
- 帮助解析代码
- 分割代码块
(2) 怎么注释?
方法一: //xxxx --> 单行注释
方法二: /*
xxxx --> 多行注释
yyyy
zzzz
*/
4. 空行与缩进
(1) 什么时候需要空行?(空白行)
空行在代码中不是必须的,但是使用了空行会让代码条理非常清晰。
(2) 什么时候需要缩进?(Tab键)
当你的程序遇到复合语句{}时,就要缩进。
缩进是为了使代码看起来条理清晰,便于后续debug和优化。
二、 编译问题
1、编译时,其实编译器做了什么东西?
其实就是在检查你的语法对不对。
2、编译之后,会出现三种结果。
(1) 编译通过。
特点: 编译完之后,会直接出现一个新的命令。
结果: 生成一个新的二进制程序。
(2) 编译警告。
例如: 不添加头文件。
特点: 编译完之后,会非常明显地看到一个warning。 --> 不解决,也可以运行,但是有会出现异常。
结果: 生成一个新的二进制程序,部分可能会执行不了。
(3) 编译出错。
例如: 缺少一个分号
特点: 编译完之后,会非常明显地看到一个error。 --> 必须要解决这个问题才可以。
结果: 没有生成新的二进制程序。
3、如果你想实现某一个功能,你已经把代码写出来,如果编译通过,那么功能就一定实现吗?
不一定。
因为编译通过,只是说明了你的代码没有语法错误,但是功能不一定写对了。
编译的方式:编译器:gcc
gcc main.c -o main
gcc:编译器/编译工具
main.c : 编译的对象
-o: output输出一个二进制程序
main:给二进制取得名字(任意都可以)
注意:我比较懒,我不想每次给程序起名字,该怎么办呢?
gcc main.c
执行结果:会在当前路径生成一个默认名字为a.out的程序
执行程序:./+程序的名字
例如: ./张三 ./a.out ./main
三、 数据类型
- 什么是数据类型
数据类型描述的是一个变量在内存中占用多少个字节的空间。
- 数据类型的种类
- 基本数据类型
在linux系统下本来就存放的数据类型,我们直接使用即可。
char、short、int、long、float、double、bool(true/flase)
-
- 非基本数据类型
在linux系统是不存在,我们必须在程序中先声明这些非基本数据类型,才可以使用。
数组、指针、结构体...
- 基本数据类型究竟可以存放什么数据?
char 字符型 --> 这种类型的变量是用于存放字符类型的数据。 short 短整型 --> 这种类型的变量是用于存放短整型的数据。 -2^15~2^15-1 int 整型 --> 这种类型的变量是用于存放整型的数据。 -2^31~2^31-1 (常用) long 长整型 --> 这种类型的变量是用于存放长整型的数据。 -2^63~2^63-1 float 单精度浮点型 --> 这种类型的变量是用于存放单精度浮点型的数据。 默认保存小数点的后6位。 (常用) double 双精度浮点型 --> 这种类型的变量是用于存放双精度浮点型的数据。 默认保存小数点的后15位。 注意:C语言中有字符串,但是没有字符串类型。 符号位: 第一位为0:正数,第一位为1:负数 内存容量的单位换算: 位(bit) --》 字节Byte --》 KB --》 MB --》 GB --》 TB 8 1024 1024 1024 1024
- 各种数据类型的输出格式
%d 十进制有符号整数 int %u 十进制无符号整数 unsigned int %f 浮点数 float %s 字符串 string %c 单个字符 char %p 指针的值 打印地址 %e 指数形式的浮点数 科学计数法,3.1415926 %x, %X 无符号以十六进制表示的整数 %o 无符号以八进制表示的整数(二进制转八进制、二进制转十六进制,八进制转十六进制) %lf 双精度 double l:long %ld 十进制有符号的整数 long int %lld long long int %hd h:half short int %hhd 打印一个字节的整数(一般针对的是char类型的数字) %% 输出百分号字符本身。 除了格式化说明符之外,printf() 函数还支持一些标志和选项,用于控制输出的精度、宽度、填充字符和对齐方式等。例如: %-10s:左对齐并占用宽度为 10 的字符串; %5.2f:右对齐并占用宽度为 5,保留两位小数的浮点数; %#x:输出带有 0x 前缀的十六进制数。 注意: 其实还有很多别的输出格式,需要学生自行百度去了解, 因为有些公司会出刁钻的格式来为难同学们
- 基本数据类型在内存中会占用多少字节?
sizeof关键词:用于计算目标内存空间占用的字节数(不是函数,而是一个运算符)。
用法:sizeof(数据类型) or sizeof(变量名)
sizeof返回值:是一个长整型数据,输出结果需要使用%ld。
结论:
1)如果在程序中声明一个int类型的变量,那么这个变量就占用4个字节。
2)基本数据类型占用的空间大小由编译系统的位数来决定的。
四、如何定义变量?
1、定义公式?
数据类型 变量名
数据类型:从基本数据类型中选择一个。
变量名:有一套规则。
规则A: 只能使用数字、字母、下划线组成。 反例:int a+5
规则B: 不能以数字开头。 反例:int 5a
规则C: 不能与系统关键词重名。 反例:int return(错误) int Return(正确)
2、定义变量的意义。
3、需要注意的问题。
1)定义一个整型变量,名字为x。
int x;
2)int x; 这行代码的含义是什么?
错误答案:定义一个整型变量,名字为x。
正确答案:在内存中连续申请4个字节的空间,然后使用变量x间接访问这片空间。
4、内存分配原则?
1)在分配空间时,内存地址一定是连续的。
2)分配空间时,内存上的空间一定是空闲的。
注意: 之前的变量已经申请过的空间不会再被申请
3)申请下来的空间位置是不确定的。
代码演示:
五、变量赋值以及生命周期、作用域。
1、变量的赋值。
定义了一个变量之后,这个变量是可以用来存储数据的,那么怎么把想存储的数据给这个变量,就要使用变量的赋值了。
使用"="对变量进行赋值,"="的作用就是把等号右边的值赋值给左边的变量。
1)定义变量的同时初始化。
int x = 100; --> 这个100其实在内存中就会存储在x所代表的那片空间中。
2)先定义,后初始化。
int x; -> 会出现随机值。
x = 100;
代码演示:
#include <stdio.h> // 测试没赋初值的情况是随机值 int main(int argc, char const *argv[]) { int a[1000]; for (int i = 0; i < 1000; i++) { printf("%d,", a[i]); } return 0; }
2、变量的生命周期以及作用域?
1)什么是生命周期?
变量从什么时候开始出现在内存空间中到什么时候从内存空间中释放。
2)什么是作用域?
作用域:这个变量能够在程序中作用到的地方。// 能访问到值的范围
根据作用域来区分可以将变量分成两类: 一类是局部变量、另一类是全局变量。
3)什么是局部变量?什么是全局变量?
在代码内的{ }内定义的变量就是局部变量。
在代码内的{ }外定义的变量就是全局变量。
注意: { }在C语言内被称之为代码块
代码演示:
4)局部变量与全局变量生命周期有什么区别?
问题一:全局变量生命周期是多少?
程序开始执行时,全局变量的空间就会申请,程序结束时,全局变量的空间就会释放。
问题二:局部变量生命周期是多少?
局部变量是函数体内申请的,在变量定义时开始申请,该函数返回(执行return语句)时,这个局部变量就会释放。
5)全局变量与局部变量作用域有什么区别?
全局变量: 一开始就申请,到程序结束才释放。 --> 在整个程序中都可以使用。
局部变量: 定义时候申请,函数返回时就释放。 --> 只是在定义局部变量的那个函数中有效。
6)全局变量与局部变量初始值分别是多少?
局部变量默认都是随机值。
全局变量默认都是0。
代码演示:
7)思考题
-
- 能不能在不同的代码块{}中定义相同的变量名?
-
- 全局变量可以跟局部变量同名吗?如果可以,会输出哪个值?
-
- 有以下的代码,请问程序的输出结果是什么?
#include<stdio.h>
int main()
{
//如何定义整型数据类型的变量呢???
//格式:数据类型 变量名 = 初始值;
//当程序运行的时候,系统会分配4个字节的内存空间,用来存储数据123,这4个字节的内存空间用变量名标识符来标识
// int a = 123;
// printf("a的地址: %p\n",&a);
// printf("a:%d\n",a);
// short b = 123; //短整型 2个字节的内存
// //长整型 8个字节
// long int d = 22;
// //如何定义无符号整型数据
// //也就是意味着 32个bit位全部用来存储数据,最高位也是用来存储数据,而不是用来符号
// unsigned int c = 123;
// //整型数据分类依据:
// //1 根据 内存大小 可分为 int long int long long int short int
// //2 根据有符号 和 无符号 有符号整数 无符号整数
// //以short int 为例子 2个字节
// //无符号 unsigned
// //该aa变量 unsigned short 值的范围: 0-65535
// //unsigned short aa = 100;
// //有符号 signed --该关键字基本都是会省略
// //short bb = 100; //值的范围 -32768 - +32767
// unsigned short e = -1; //0-65535
// //无符号char类型 -----占1个字节
// unsigned char a1 = -1; // 0 - 255
// // %d --》代表有符号的4个字节的整数
// // %hd -->half 一半 4个字节的一半 就是 2个字节 short int
// // %hhd 一个字节,代表有符号的一个字节
// // %u -->代表 无符号的4个字节的整数
// //%hu --》half 无符号的2个字节的整数
// //%hhu -->无符号的1个字节的整数
// printf("%hhu\n",a1);
// //有符号的char类型 --占1个字节
// //值域范围 -128 ~ +127
// char a2 = -130;
// printf("a2:%hhd\n",a2);
// //无符号 int类型
// //值域范围: 0~4294967295(2^32-1)
// unsigned int a3 = -1;
// printf("a3:%u\n",a3);
// //unsigned short a4 = -5;
// //0 ~ 65535
// //请问打印出来等于多少 %hu 65531
// //数据以内存中存放的形式进行运算,也就是以补码的方式进行运算
// short a4 = 10;
// short a5 = -3;
// short a6 = a4+a5;
// a6= 10+(-3)=7
// unsigned char a = 255;
// char b = 255;
// printf("%d %u\n",a,a); //255 255
// printf("%d %u\n",b,b); //-1 4294967295
int a = -2;
printf("%d %u\n",a,a);
return 0;
}
六、原码、反码、补码
原码反码补码————》整数在内存中的存储方式
计算机内以二进制,满2进1,以8位char为例,1111 1111如果加1的话吗,不是1 0000 0000, 而是直接归零 0000 0000
- 原码
- 正数: 用第一位表示符号位为0,其余位表示值
- 负数: 用第一位表示符号位为1,其余位表示值
- [+1]原 = 0000 0001
- [-1]原 = 1000 0001
- 反码
- 正数: 跟原码相同
- 负数: 符号位不变,其余位取反
- [+1]反 = 0000 0001
- [-1]反 = 1111 1110
- 补码:
- 正数: 跟原码相同
- 负数: 在反码的基础上+1
- [+1]补 = 0000 0001
-
- [-1]补 = 1111 1111
- 使用原码、反码、补码的原因
- 我们刚刚了解到在计算机中可以用三种编码方式来表示一个数
- 因为正数的三种表示都一样,不做过多的讨论
- 主要要研究的是负数,在我们人脑来理解的话,原码是最好理解,为什么要出现反码以及补码呢?
因为人脑肯定是知道第一位是符号位,人在计算的时候,会自动区分符号位,并且对真值区域进行加减。对于计算机来说,加减乘除本身就是最基础的运算,要设计的尽量简单。计算机如果说要去辨别符号位,显然会让计算机的基础电路设计的十分复杂! 于是人们想出一个解决方法:就是把符号位也加入到运算中去。根据运算法则中,一个整数-另一个整数等于加上一个负数,例如:1-1 =1+(-1),可以让计算机的运算变得简单。
注意: 计算机内没有减法。
- 首先看原码的计算
[+1]原 = 0000 0001
[-1]原 = 1000 0001
[-2]原 错误)
- 其次看反码的计算
[+1]反 = 0000 0001
[-1]反 = 1111 1110
1111 1111 (反码)--》1000 0000(原码) == -0
如果用反码来计算减法,结果是真值部分是对的,但是符号位出现了一个负号,虽然我们知道+0和-0其实是一样的,但是0带了一个负号是没有任何意义的,并且会出现两个编码表示一个数的情况,这在我们的计算机内不被允许的。
- 最后看补码的运算
[+1]补 = 0000 0001
[-1]补 = 1111 1111
0000 0000 (补码) == 0(原码)
解决了两个编码表示一个数的情况,而且不会出现矛盾。
- 综上所述
学过数电、模电的同学们,可以看出来,这个运算的核心是模运算
例如: 8-2 = 8+10
得出结论: A - B = A+模-B
计算机内以8位二进制来说: A - B = A+256 -B
8-1=8+256-1=8+255 = 7
0000 1000
1111 1111
0000 0111 --》 7
256-B = 255+1-B = 255-B+1
255-B按位取反——反码
在反码的基础上+1 就是补码,这也是补码的由来
结论:
正数的原码反码补码相同
负数的原码:最高位符号位为1,其余为真值部分
负数的反码:在原码的基础上,最高位符号位不变,其余位取反
负数的补码:在反码的基础上+1
今日练习:
char a1 = -9;
short a2 = -10;
a1 a2 在内存中是如何存储的?
short a3 = 10;
short a4 = -8;
short a5 = a3 + a4; // 2
在内存中是如何运算的?补码进行计算