前端学习C语言 – 开篇
前端学习C语言有很多理由:工作、兴趣或其他。
C 语言几个常见的使用场景:
-
操作系统
开发:Linux 操作系统的内核就是主要由 C 语言编写的。其他操作系统也广泛使用 C 语言进行核心部分的开发。 - 系统级开发和嵌入式编程:C 语言具有强大的
底层
控制能力和高效的代码执行效率,非常适合进行系统级开发和嵌入式编程。 - 游戏开发和图形处理:许多游戏引擎和各种图形应用程序都是使用 C 语言进行开发的。这是因为 C 语言相对于其他高级语言具有更好的性能和更直接的
硬件
控制能力,可以实现复杂的计算和高性能的图形处理。 - 数据库系统开发:C 语言的高效性使得其在数据库系统的内部组件中使用非常广泛。例如,Berkeley DB、
MySQL
、PostgreSQL 等数据库管理系统都是使用 C 语言进行开发的。 - 底层驱动开发:由于 C 语言直接操作内存和
硬件
资源,可以用来编写驱动程序、设备控制器和微控制器的固件等低级别的代码。 - 单片机(跟硬件打交道)
C 语言还可以用于许多其他方面的开发,包括网络编程、信号处理、人工智能等。
第一个程序
在 linux 中创建一个 .c
文件:
pjl@pjl-pc:~/$ cat hello.c
#include // 头文件。提供了 I/O 功能,例如下面用的 printf() 函数
// int 函数返回值的类型
// main 入口函数,通常有且只有一个。没有 main 在 linux 中也可以编译通过,比如 gcc -nostartfiles
int main(void) {
// 必须加分号,否则编译会报错
printf("Hello Worldn");
return 0;
}
编译
这里使用 gcc
来编译 c。
Tip:gcc 代表GNU Compiler Collection,是一个开源的编译器集合,由GNU项目开发。它主要用于编译C和C++等编程语言的源代码,并生成可执行程序
然后安装 gcc:
// 更新源
pjl@pjl-pc:~/$ sudo apt update
// 安装 gcc
pjl@pjl-pc:~/$ sudo apt-get install gcc
通过 gcc -v
确认以安装:
pjl@pjl-pc:~/$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
....
gcc version 9.3.0 (Ubuntu 9.3.0-10kylin2)
编译 hello.c
生成可执行文件 hello
,通常没有扩展名,直接运行可执行文件,输出 Hello World
。就像这样:
pjl@pjl-pc:~/$ gcc hello.c -o hello
pjl@pjl-pc:~/$ ls
hello hello.c stdio.h
// 直接运行
pjl@pjl-pc:~/$ ./hello
Hello World
例如故意去掉 printf() 函数后的分号
,编译就会报错。就像这样:
pjl@pjl-pc:~/$ gcc hello.c -o hello
hello.c: In function ‘main’:
hello.c:4:28: error: expected ‘;’ before ‘return’
4 | printf("Hello Worldn")
| ^
| ;
5 | return 0;
| ~~~~~~
Tip: 笔者找了两款在线编写 c 的环境,直接点击运行
即可查看结果。语法错误也无需等待编译后查看。:
-
lightly – 点击
在线使用
,使用微信扫一下即可登录,通过云能保存自己的项目。 - dotcpp – 运行一次需要等20秒,可能为了缓解服务器压力。
头文件
是 C 标准库的
头文件
之一,包含了许多与标准输入输出流(stdin、stdout 和 stderr)相关的函数和常量定义。
常见的函数包括:
- printf() 和 fprintf():格式化
输出函数
,可以将内容输出到终端或文件中; - scanf() 和 fscanf():格式化读取函数,可以从终端或文件中读取输入数据;
- fgets() 和 fputs():以行为单位进行读写,可以读写字符串;
- fgetc() 和 fputc():以字符为单位进行读写;
- fseek() 和 ftell():文件定位函数,用于在文件中移动位置以及得到文件当前位置。
此外,头文件还包含许多输入和输出流相关的常量和宏定义,例如常见的 stdin、stdout、stderr 流对象指针等等。
总之, 是 C 语言程序中使用最频繁的头文件之一,提供了强大的
I/O
功能支持,方便程序员完成输入输出操作。
在 Linux 系统中, 一般存放在
/usr/include
目录下
pjl@pjl-pc:~/$ sudo find / -name stdio.h
输入密码
find: ‘/run/user/1000/gvfs’: 权限不够
/opt/linux-stable/tools/include/nolibc/stdio.h
/opt/linux-stable/arch/powerpc/boot/stdio.h
// stdio.h 就是这个
/usr/include/stdio.h
/usr/include/x86_64-linux-gnu/bits/stdio.h
/usr/share/python3-pycparser/fake_libc_include/stdio.h
基本数据类型
要存储数据,就得申请内存,就得告诉操作系统要申请多少字节,于是 c 语言就是通过不同的类型申请内存。
基本数据类型有:
空类型 void,常用于函数返回值,或指针,不会用于变量
字符型
数值型
实型 - 表示小数
单精度 float 精确到小数点6位
双精度 doubble 精确到小数点15位,表示的数也更大
整型
短整型
有符号短整型
无符号短整型
整型
有符号整型
无符号整型
长整型
有符号长整型
无符号长整型
重点说一下整型
。其中无符号
整数类型(unsigned
int)只能表示非负整数,有符号整型则可以表示正数和负数。
# 无符号
unsigned int variable_name;
# 有符号
int variable_name;
类型所占字节
字节
是内存中最基本单元。
每个字节由8位组成,每一位将只有两种可能的状态:1或0。因此,一个字节可以表示256个不同的值(2^8=256),包括从0到255的所有数字。
Tip
:由于计算机芯片上只有两个状态,通常为高电平
和低电平
,因此计算机只能处理0和1。虽然二进制数系统看起来比较简单,但它可以用来表示任意数字、字符和其他类型的数据。例如,整数和浮点数可以用二进制数表示,字符串可以将字符转换为二进制代码并存储在内存中。因此,虽然计算机只能使用0和1,但它们可以处理和存储各种不同类型的数据。
在C语言中,通常使用16进制表示内存地址
,比如 0x100
、0x101
。把每个字节比作一个房间,内存中有太多房间,16进制比10进制数字看起来要小点,比如同样是数字10,16进制就能表示16个房间。
十进制
0 1 2 3 4 5 6 7 8 9 在加1 变成10
二进制
0 1 在加1 变成10
八进制
0 1 2 3 4 5 6 7 在加1 变成10
十六进制
0 1 2 3 4 5 6 7 8 9 A B C D E F 在加1 10
比如 long 在 64位系统中占8个字节,在32位系统中占4个字节。可以通过 sizeof
计算每种类型在系统中所占字节数。就像这样:
// 代码
pjl@pjl-pc:~/$ cat demo-4.c
#include
int main() {
printf("char类型所占字节数为:%zun", sizeof(char));
printf("short类型所占字节数为:%zun", sizeof(short));
printf("int类型所占字节数为:%zun", sizeof(int));
printf("long类型所占字节数为:%zun", sizeof(long));
printf("float类型所占字节数为:%zun", sizeof(float));
printf("double类型所占字节数为:%zun", sizeof(double));
printf("long long类型所占字节数为:%zun", sizeof(long long));
return 0;
}
// 编译
pjl@pjl-pc:~/$ gcc demo-4.c -o demo-4
// 运行可执行文件
pjl@pjl-pc:~/$ ./demo-4
char类型所占字节数为:1
short类型所占字节数为:2
int类型所占字节数为:4
long类型所占字节数为:8
float类型所占字节数为:4
double类型所占字节数为:8
long long类型所占字节数为:8
Tip: sizeof 是一个关键字,虽然用法和函数类似,如果sizeof()是函数,那么 sizeof() 中的 a++ 就会执行,将结果作为实参传入 sizeof,那么结果应该是 12,然而结果是 11。sizeof(a++)
等同于 sizeof(int)
。
#include
int main(void) {
int a = 10;
a++;
printf("%lun", sizeof(a++));
printf("%dn", a);
return 0;
}
/*
4
11
*/
类型的范围
我们首先详细分析char能表示的范围,其他类型计算的方法也类似。
char
char,占1个字节,也就是8位。
无符号,最小是8位都是0,也就是0,最大就是8位全是1,也就是255。计算过程如下:
// 8位都是1
1111 1111
// 加1得到 2^8 - 1,即255
+ 1
1 0000 0000 = 2^8 = Math.pow(2, 8) = 256
256 - 1 = 255
Tip: 比如二进制 1000 就是 Math.pow(2, 3),也就是8。
无符合 char 的范围是 0 ~ 255
,共 256 种。
有符号,最高位(即最左侧)0 表示正数,1 表示负数。正数则是 0111 1111
,也就是 127。
// 0 表示正数
0111 1111
+1
1000 0000 = 2^7 = 128
128 - 1 = 127
负数则是 1000 0000
,而不是 1111 1111
(通常我们认为负的值越大,就越小)。假如是后者,有符号的范围就是 -127 ~ 127,共255中可能,而无符号有256中可能。
1111 1111
// 只看这7位
111 1111
+1
1000 0000 = 2^7 = 128
128 - 1 = 127
少的一个在这里,一个正0,一个负0,数学中没有这个说法,所以在计算机中是这么规定的,最小数是 1000 0000
,也就是 -128。
// 正0
0000 0000
// 负0
1000 0000
有符合 char 的范围是 -128 ~ 127
,也是 256 种。
有符号整型
singed char 1 -2^7(-128) ~ 2^7-1(127)
short int 或 short 2 -2^15 ~ 2^15 -1
int 4 -2^31 ~ 2^31 -1
long 或 long int 4(32位) 8(64位) -2^31 ~ 2^31 -1
long long 或 long long int 8 -2^63 ~ 2^63 -1
Tip
: short 和 short int 等价;long 和 long int 等价;long long 和 long long int 等价。
无符号整型
unsinged char 1 0~2^8 -1 (255)
unsinged short 2 0~2^16 -1(65535)
unsinged int 4 0~2^32 -1(4294 9672 95)
unsinged long 4(32位) 8(64位) 0~2^32 -1
unsinged long long 8 0~2^64 -1
浮点
float 是4个字节,32位,范围是 -3.40282347e+38 ~ 3.40282347e+38。最大有38个数。
unsinged int 同样也是 4个字节,范围却只有 10个数。
同样是 4 个字节,为何 float 能表示的数更大?
这是因为它的表示方法不一样:最左侧 1 位表示符号,中间 8 位8 位表示指数
,最后 23 位为尾数
。符号位用来表示数字的正负,指数位则表示小数点在有效数字中向左或向右移动多少位(即阶码
),而尾数位则包含实际的有效数字
。
单精度浮点
(float)数采用了分数形式的科学计数法(即尾数和指数分别表示大数整数部分和小数部分的方式)来表示实数,但由于单精度浮点数的尾数只有 23 位,而指数只有 8 位,因此这种近似值可能会存在精度误差,导致与原来的值略有不同。
双精度浮点数
(double)和单精度 float 一样由三部分组成。双精度浮点数使用更多的位数表示指数
和尾数
,因此可以表示更广阔范围的实数,并具有更高的精度。
长双精度浮点数
(long double)也由三部分组成:符号位、指数和尾数。长双精度浮点数的规格并没有在 IEEE 754 中具体规定。因此这一点取决于不同实现及其所用硬件平台的架构。在 x86 架构下,一般将 long double 定义为 80 位或者 128 位,而在 ARM 架构下则分别将其定义为 96 位和 128 位。通常情况下它的存储长度
要比双精度浮点数更长,同时在精度
方面也有所提升。
练习
char的计算
#include
int main(void) {
char a = -128;
char num = a -1;
printf("%d n", num);
}
答案是 127
。过程如下:
- num 是 char 类型,范围是 -128 ~ 127,所以不可能是 -129
- 计算机中,有许多处理负数的方式,其中补码是最常见和普遍的一种。
- -128 源码是
1000 0000
,取反码1111 1111
(标志位不动,其他取反。0变1,1变0),反码加1得补码1000 0000
源码 1000 0000
反码 1111 1111
补码 1 1000 0000 —— 由于只有七位,删除最高位(1000 0000)得到 000 0000
1000 0000
- -1 的补码是
1111 1111
:
源码 1000 0001
反码 1111 1110
补码 1111 1111
- 两个补码相加得
0111 1111
,也就是 127
1000 0000
1111 1111
1 0111 1111 —— 只有8位,删除最高位
0111 1111
有符号转无符号
pjl@pjl-pc:~/$ cat demo-3.c
#include
int main(void) {
int i = -20;
unsigned int j = 10;
if(i + j > 0){
printf("i + j > 0n");
}
printf("%un", i + j);
}
结果是:4294967286。
pjl@pjl-pc:~/$ gcc demo-3.c -o demo-3
pjl@pjl-pc:~/$ ./demo-3
i + j > 0
4294967286
解答过程:i是有符号int,j是无符号int,需要将有符号转为无符号。i是负数,得转为补码,最高位就不再表示符号,需要参与计算,补码转为十进制是 4294967276,在加上 10 就是结果。
-20
源码 10000000 00000000 00000000 00010100
反码 11111111 11111111 11111111 11101011
补码 11111111 11111111 11111111 11101100
现在最高位也得参与运算,打开 windows 的计算器,从`查看`进入`程序员`,选择`二进制`,将补码粘贴,在点击`十进制`,得到4294967276
Tip:类型不同,比如 char + int,把1个字节的 char 转为 4 个字节的 int 很好理解。就是范围小的转为范围大的。比如有符号char 的范围是 -128~127
,无符号 char 的范围是 0~255
,计算机认为无符号的255比有符号的127更大!?
int的计算
#include
int main(void) {
unsigned int a = 3;
printf("%un", -1 * a);
}
结果是 4294967293
。
结果分析:
- -1 转为补码,补码对应的十进制:4294967295
- 4294967295*3 结果12884901885,12884901885的二进制是
1011 11111111 11111111 11111111 11111101
- int 就4个字节,也就是32位,这里是36位,超出int。删除最高4位,得到
11111111 11111111 11111111 11111101
,转为十进制就是 4294967293。
-1
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 11111111 11111111 11111111
补码对应的十进制:4294967295
4294967295*3
1011 11111111 11111111 11111111 11111101
删除高4位,值保留int的32位
11111111 11111111 11111111 11111101
小数的二进制表示
小数1.5625的二进制表示?
答案:1.1001。步骤如下:
首先,将1.5625转化为二进制整数部分1。
然后,将小数部分0.5625乘以2,得到1.125。将其整数部分1添加到二进制小数部分的末尾,得到1.1。
继续将小数部分0.125乘以2,得到0.25。将其整数部分0添加到二进制小数部分的末尾,得到1.10。
继续将小数部分0.25乘以2,得到0.50。将其整数部分0添加到二进制小数部分的末尾,得到1.100。
最后,将小数部分0.50继续乘以2,得到1.00。将其整数部分1添加到二进制小数部分的末尾,得到1.1001。
因此,1.5625的二进制表示为1.1001
扩展
计算机如何把人类理解的信息转为0和1
将人类理解的信息转换成0和1,需要使用编码方式来表示不同类型的信息。这个过程通常称为数字化。
例如,对于文字信息,可以使用ASCII编码或Unicode编码将每个字符映射到一个二进制数字。比如字母“A”在ASCII编码中对应的二进制数字是01000001,字母“B”对应的是01000010等等。通过这种方式,计算机可以识别并处理文本信息。
对于图像和音频等复杂的信息,数字化的过程更为复杂,需要使用更加高级的编码方式和算法来实现。例如,JPEG、PNG等格式用于存储图片,MP3、AAC等格式用于存储音频。这些格式都会将图像和音频分割成小块并使用压缩算法来减小文件大小,以节省存储空间和传输带宽,并且保持足够的质量以满足人类对图像和声音的要求。
总之,将人类理解的信息转换成0和1,是通过使用不同的编码方式来表示数据,从而使计算机能够理解和处理这些数据。
stdio.h 头文件
将该文件导出:
pjl@pjl-pc:~/$ cat /usr/include/stdio.h > stdio.h
stdio.h 文件一共 875
行,完整内容如下,例如 printf
等函数就在里面:
/* Define ISO C stdio on top of C++ iostreams.
Copyright (C) 1991-2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
. */
/*
* ISO C99 Standard: 7.19 Input/output
*/
#ifndef _STDIO_H
#define _STDIO_H 1
#define __GLIBC_INTERNAL_STARTING_HEADER_IMPLEMENTATION
#include
__BEGIN_DECLS
#define __need_size_t
#define __need_NULL
#include
#define __need___va_list
#include
#include
#include
#include
#include
#include
#include
#ifdef __USE_GNU
# include
#endif
#if defined __USE_XOPEN || defined __USE_XOPEN2K8
# ifdef __GNUC__
# ifndef _VA_LIST_DEFINED
typedef __gnuc_va_list va_list;
# define _VA_LIST_DEFINED
# endif
# else
# include
# endif
#endif
#if defined __USE_UNIX98 || defined __USE_XOPEN2K
# ifndef __off_t_defined
# ifndef __USE_FILE_OFFSET64
typedef __off_t off_t;
# else
typedef __off64_t off_t;
# endif
# define __off_t_defined
# endif
# if defined __USE_LARGEFILE64 && !defined __off64_t_defined
typedef __off64_t off64_t;
# define __off64_t_defined
# endif
#endif
#ifdef __USE_XOPEN2K8
# ifndef __ssize_t_defined
typedef __ssize_t ssize_t;
# define __ssize_t_defined
# endif
#endif
/* The type of the second argument to `fgetpos' and `fsetpos'. */
#ifndef __USE_FILE_OFFSET64
typedef __fpos_t fpos_t;
#else
typedef __fpos64_t fpos_t;
#endif
#ifdef __USE_LARGEFILE64
typedef __fpos64_t fpos64_t;
#endif
/* The possibilities for the third argument to `setvbuf'. */
#define _IOFBF 0 /* Fully buffered. */
#define _IOLBF 1 /* Line buffered. */
#define _IONBF 2 /* No buffering. */
/* Default buffer size. */
#define BUFSIZ 8192
/* The value returned by fgetc and similar functions to indicate the
end of the file. */
#define EOF (-1)
/* The possibilities for the third argument to `fseek'.
These values should not be changed. */
#define SEEK_SET 0 /* Seek from beginning of file. */
#define SEEK_CUR 1 /* Seek from current position. */
#define SEEK_END 2 /* Seek from end of file. */
#ifdef __USE_GNU
# define SEEK_DATA 3 /* Seek to next data. */
# define SEEK_HOLE 4 /* Seek to next hole. */
#endif
#if defined __USE_MISC || defined __USE_XOPEN
/* Default path prefix for `tempnam' and `tmpnam'. */
# define P_tmpdir "/tmp"
#endif
/* Get the values:
L_tmpnam How long an array of chars must be to be passed to `tmpnam'.
TMP_MAX The minimum number of unique filenames generated by tmpnam
(and tempnam when it uses tmpnam's name space),
or tempnam (the two are separate).
L_ctermid How long an array to pass to `ctermid'.
L_cuserid How long an array to pass to `cuserid'.
FOPEN_MAX Minimum number of files that can be open at once.
FILENAME_MAX Maximum length of a filename. */
#include
/* Standard streams. */
extern FILE *stdin; /* Standard input stream. */
extern FILE *stdout; /* Standard output stream. */
extern FILE *stderr; /* Standard error output stream. */
/* C89/C99 say they're macros. Make them happy. */
#define stdin stdin
#define stdout stdout
#define stderr stderr
/* Remove file FILENAME. */
extern int remove (const char *__filename) __THROW;
/* Rename file OLD to NEW. */
extern int rename (const char *__old, const char *__new) __THROW;
#ifdef __USE_ATFILE
/* Rename file OLD relative to OLDFD to NEW relative to NEWFD. */
extern int renameat (int __oldfd, const char *__old, int __newfd,
const char *__new) __THROW;
#endif
#ifdef __USE_GNU
/* Flags for renameat2. */
# define RENAME_NOREPLACE (1
#ifdef __USE_POSIX
/* Return the system file descriptor for STREAM. */
extern int fileno (FILE *__stream) __THROW __wur;
#endif /* Use POSIX. */
#ifdef __USE_MISC
/* Faster version when locking is not required. */
extern int fileno_unlocked (FILE *__stream) __THROW __wur;
#endif
#ifdef __USE_POSIX2
/* Create a new stream connected to a pipe running the given command.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern FILE *popen (const char *__command, const char *__modes) __wur;
/* Close a stream opened by popen and return the status of its child.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int pclose (FILE *__stream);
#endif
#ifdef __USE_POSIX
/* Return the name of the controlling terminal. */
extern char *ctermid (char *__s) __THROW;
#endif /* Use POSIX. */
#if (defined __USE_XOPEN && !defined __USE_XOPEN2K) || defined __USE_GNU
/* Return the name of the current user. */
extern char *cuserid (char *__s);
#endif /* Use X/Open, but not issue 6. */
#ifdef __USE_GNU
struct obstack; /* See . */
/* Write formatted output to an obstack. */
extern int obstack_printf (struct obstack *__restrict __obstack,
const char *__restrict __format, ...)
__THROWNL __attribute__ ((__format__ (__printf__, 2, 3)));
extern int obstack_vprintf (struct obstack *__restrict __obstack,
const char *__restrict __format,
__gnuc_va_list __args)
__THROWNL __attribute__ ((__format__ (__printf__, 2, 0)));
#endif /* Use GNU. */
#ifdef __USE_POSIX199506
/* These are defined in POSIX.1:1996. */
/* Acquire ownership of STREAM. */
extern void flockfile (FILE *__stream) __THROW;
/* Try to acquire ownership of STREAM but do not block if it is not
possible. */
extern int ftrylockfile (FILE *__stream) __THROW __wur;
/* Relinquish the ownership granted for STREAM. */
extern void funlockfile (FILE *__stream) __THROW;
#endif /* POSIX */
#if defined __USE_XOPEN && !defined __USE_XOPEN2K && !defined __USE_GNU
/* X/Open Issues 1-5 required getopt to be declared in this
header. It was removed in Issue 6. GNU follows Issue 6. */
# include
#endif
/* Slow-path routines used by the optimized inline functions in
bits/stdio.h. */
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
/* If we are compiling with optimizing read this file. It contains
several optimizing inline functions and macros. */
#ifdef __USE_EXTERN_INLINES
# include
#endif
#if __USE_FORTIFY_LEVEL > 0 && defined __fortify_function
# include
#endif
#ifdef __LDBL_COMPAT
# include
#endif
__END_DECLS
#endif /* included. */
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 众惠生活是一种新型的生活方式,共享资源,互相帮助,共同发展
众惠生活是一种新型的生活方式,它提倡人们共享资源、互相帮助、共同发展。在这样的生活方式中,大家可以共同享有物质资源,分享知识技能,共同探讨问题,共同解决难题,从而实现共同进步和共同繁荣。 众惠生活有着许多优点。首先,它可以节约资源。在传统的生活方式中,每个人都…