3小时学会C语言横向对比/纵向剖析,轻松学习C语言
Posted Cry丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3小时学会C语言横向对比/纵向剖析,轻松学习C语言相关的知识,希望对你有一定的参考价值。
文章目录
前言
😃 沒有前言 😃
1.前置知识
1.1 序
计算机实际上是由底层一系列的数字电路实现的,通过输入电平的高(1)和低(0)和寄存器之间的组织关系来决定输出的结果,这个过程就是计算器的计算能力。
小学2年级的时候老师教过我们,最开始计算机是通过打孔来实现变成的,比如打了孔就代表输入1,不打孔就输入0,但是这样很麻烦,后来为了提高开发者的开发效率,就封装了一些具体的功能例如:加减乘除,映射到某些语句,只需要输入这些语句,底层的数字电路就会去实现对应的功能,汇编语言就诞生了。
但是汇编语言虽然比机器语言好用了很多,但是还是很麻烦,之后开发者们开发出了一系列语言,其中C语言就是比较火热的高级语言之一,在C的基础上又衍生出了C++,之后为了更便于开发者们的上手以及考虑到不同环境的隔离,又衍生出了Java,Python这些抽象程度更高的语言。
1.2 编译器和解释器
会出现上面这些语言迭代的根本原因,本质上是因为编译器越来越智能了,功能越来越强大,能够更加"人性化、智能化"地把我们写的高级语言最终翻译成底层的0/1机器码,其中编译又可以分为静态编译和动态编译。
📔静态编译:
所谓静态编译,就是把代码编译完成0/1机器码后再去运行,比如c语言的gcc编译器,c++的g++,都是基于这个原理。
📔动态编译:
所谓动态编译通过存在于解释型语言中,就是在代码运行过程中,解释器发现某段代码运行的特别频繁,他就会直接把这段代码做一个"运行时编译",直接编译成机器码,之后访问这段代码就直接执行,而不会走解释器的逻辑。
📔编译:
写到这里感觉还是有必要解释一下编译和解释的区别,因为有些小伙伴对这个概念的理解不够精准。通俗易懂的说,编译就是一段代码在运行前,要先编译成机器码,然后运行时会对这段机器码进行一个直接的执行,例如windows下的.exe程序。但是这种做法显然会受到操作系统环境的影响,所以比较典型的编译型语言有:c/c++,有些语言他比较"智能"(虽然其实也很笨了),他兼具了这种特性,其实他是一种解释性语言,但是在运行时会动态判断当前运行的代码是不是很"热",如果很"热"就给他"加个缓存",这个就是动态编译,比较典型的就是java中的JIT动态编译器。
📔解释:
比较典型的有python、java,都是这一类,解释器会直接把我们手敲的源代码直接解析成中间的字节码,然后在运行时把字节码解释成机器码进行执行。这种因为有个中间态,所以运行效率相对纯粹的编译型语言会低一点,可以通过兼容动态编译的方式进行优化。其中python的解释器会因为运行平台的不同而不同,java的解释器由jvm虚拟机决定。
📔运行效率:
静态编译 约等于 动态编译 > jvm模板解释器 > 解释器
2.C语言技术点及应用技巧
因为博主本身对java比较熟悉,我会尽量对比着java和c的差别来讲解c语言,如果有其他语言比较熟悉的小伙伴也可以对比着来复习c语言,会有一些不一样的体会。
本文章涉及的C内容对于指针暂先不详细讲解,但是会用到也会提到。详细讲解我会再开一章,因为这个确实很重要。还有对于结构体、共用体等边边角角的部分,我也会放到下一篇讲解。下面开始进入正题(偶尔涉及的一些奇奇怪怪的知识,有兴趣的可以自行了解)。
讲解顺序:
数据结构
运算符
循环结构
条件判断
函数
数组
字符串
2.1 调试用IDE
📔 windows: visual studio,clion
📔 linux: clion
勤劳是中华民族的传统美德,这边大家自行通过搜索引擎下载一下即可,简单了解下使用就可以上手了。博主windows下用的是vs2013,毕竟微软亲儿子性能肯定会更高一点
2.2 数据类型
2.2.1 c
📔 c:windows(ide设置32位)
数据类型 | 占用空间(字节) |
---|---|
char | 1 |
short | 2 |
int | 4 |
long | 4 |
float | 4 |
double | 8 |
📔 c:linux(这个和不同linux-os有关)
数据类型 | 占用空间(字节) |
---|---|
char | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
可以通过sizeof()
测试,比如:
long a = 6;
printf("long型变量的长度: %d\\n", sizeof(a));
📔注意:c语言中是没有原生的布尔类型的(bool),他有2种实现方式,一种是通过枚举来实现,例如:
enum Bool
FALSE(0),
TRUE(1)
另外一种是通过#define
来直接定义常量,例如:
#define TRUE 1
#define FALSE 0
📔c语言中所有的数据类型还又分为: 有符号数,和无符号数,无符号数需要在定义时显式添加关键字: unsigned
,不添加就默认是有符号数,他们之间的区别就是有符号数的最高位代表符号位:0代表正数,1代表负数。
例如:255对应的16进制是0xff,
char a = 0xff;
printf("%d\\n", a);
unsigned char b = 0xff;
printf("%d\\n", b);
补充:c语言中的不同数据类型,事实上就对应着不同的寄存器大小,反过来我们在看不同寄存器的时候,也可以理解成不同的变量。
|63..32|31..16|15-8|7-0|
|AH.|AL.|
|AX.....|
|EAX............|
|RAX...................|
2.2.2 java
数据类型 | 占用空间(字节) |
---|---|
boolean | 1 |
byte | 1 |
char | 2 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
2.3 运算符
不同语言之间,运算符这一块基本可以认为是相同的,没有太大差异。
📔算术运算符:
📔关系运算符:
📔逻辑运算符
📔位运算符
📔赋值运算符
其他不常用的遇到了再去搜索一下。
2.4 条件判断
不同语言之间,基本相同。
if :单独判断
if(condition)
statement(s);
if - else if:多分支判断
if(condition1)
statement(s);
else if(condition2)
statement(s);
if - else if - else:带else的多分支判断
if(condition1)
statement(s);
else if(condition2)
statement(s);
else
statement(s);
例如:
#include <stdio.h>
int main ()
/* 局部变量定义 */
int a = 10;
/* 使用 if 语句检查布尔条件 */
if( a < 20 )
/* 如果条件为真,则输出下面的语句 */
printf("a 小于 20\\n" );
printf("a 的值是 %d\\n", a);
return 0;
输出:
a 小于 20
a 的值是 10
2.5 循环
不同语言之间,基本相同。
while:先判断后执行
while(condition)
statement(s);
do while:先执行后判断
do
statement(s);
while( condition );
for循环:
for ( init; condition; increment )
statement(s);
例如:
#include <stdio.h>
int main ()
/* for 循环执行 */
for( int a = 10; a < 20; a = a + 1 )
printf("a 的值: %d\\n", a);
return 0;
2.6 函数
2.6.1 函数的基本使用
📔函数定义格式 ,跟Java一样的
📔声明与定义:可以分开,也可以不分开
c语言中的函数如果不声明的话,必须按顺序写,调用;否则就必须要函数声明
int t1()
return t2();
int t2()
return 10;
如果不写函数声明,因为t2()
的定义在t1()
之后,函数调用会报错,在开头前加一个函数声明即可int t2();
java中因为他用到了c++的动态绑定,所以不存在函数调用的顺序问题。
2.6.2 函数的可变参数:va_list
VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。
用法实例:
#include <stdarg.h>
int AveInt(int,...);
void main()
printf("%d/t",AveInt(2,2,3));
printf("%d/t",AveInt(4,2,4,6,8));
return;
int AveInt(int v,...)
int ReturnValue=0;
int i=v;
va_list ap ;
va_start(ap,v);
while(i>0)
ReturnValue+=va_arg(ap,int) ;
i--;
}
va_end(ap);
return ReturnValue/=v;
可参考:va_list使用方法
2.6.3 函数的入参
📔传值调用
默认情况下,C 语言使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。函数 swap() 定义如下:
/* 函数定义 */
void swap(int x, int y)
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
return;
现在,让我们通过传递实际参数来调用函数 swap():
#include <stdio.h>
/* 函数声明 */
void swap(int x, int y);
int main ()
/* 局部变量定义 */
int a = 100;
int b = 200;
printf("交换前,a 的值: %d\\n", a );
printf("交换前,b 的值: %d\\n", b );
/* 调用函数来交换值 */
swap(a, b);
printf("交换后,a 的值: %d\\n", a );
printf("交换后,b 的值: %d\\n", b );
return 0;
当上面的代码被编译和执行时,它会产生下列结果:
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 100
交换后,b 的值: 200
📔引用调用
通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
/* 函数定义 */
void swap(int *x, int *y)
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */
return;
现在,让我们通过引用传值来调用函数 swap():
#include <stdio.h>
/* 函数声明 */
void swap(int *x, int *y);
int main ()
/* 局部变量定义 */
int a = 100;
int b = 200;
printf("交换前,a 的值: %d\\n", a );
printf("交换前,b 的值: %d\\n", b );
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
printf("交换后,a 的值: %d\\n", a );
printf("交换后,b 的值: %d\\n", b );
return 0;
当上面的代码被编译和执行时,它会产生下列结果:
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
📔与java的比较:
很显然可以看入参如果传入的是某个变量的地址&,也就相当于变成了java中的引用数据类型,这也是c语言操作基本数据类型的一个灵活之处。
2.6.4 输出打印
c语言一般用printf函数,c++一般用cout。但是cout没有printf好用
#include <stdio.h>
int main()
printf("hello world\\n");
return 0;
格式化参数:
%d 十进制有符号整数(常用)
%u 十进制无符号整数(常用)
%f 浮点数
%s 字符串(常用)
%c 单个字符(常用)
%p 指针的值(常用)
%e 指数形式的浮点数
%x, %X 无符号以十六进制表示的整数(常用,打印指针一般会用到这个)
%o 无符号以八进制表示的整数
%g 把输出的值按照%e或者%f类型中输出长度较小的方式输出
%p 输出地址符
%lu 32位无符号整数
%llu 64位无符号整数
可以在“%”和字母之间插进数字表示最大场宽
%3d表示输出3位整型数,不够3位右对齐
其他常用函数可以查文档
2.7 数组
c中的数组一般都是配合指针用的,非常灵活
2.7.1 基本使用
int arr[3] = 1, 2, 3 ;
也可以不指定初始值,不指定的时候就是初始元素个数
举例:
#include <stdio.h>
int main ()
int n[ 10 ]; /* n 是一个包含 10 个整数的数组 */
int i,j;
/* 初始化数组元素 */
for ( i = 0; i < 10; i++ )
n[ i ] = i + 100; /* 设置元素 i 为 i + 100 */
/* 输出数组中每个元素的值 */
for (j = 0; j < 10; j++ )
printf("Element[%d] = %d\\n", j, n[j] );
return 0;
编译运行会输出数组中的每个元素
2.7.2 如何获得数组元素个数
c中没有类似java中的size()方法,可以直接获取数组中的元素个数,需要手动计算
int len = sizeof(arr) / sizeof(short);
2.7.3 数组作为函数参数
📔三种写法(一般前两种写法)
void myFunction(int param[]);
void myFunction(int *param);
void myFunction(int param[10]);
数组作为函数参数,它的本质是引用传值
void change(char* arr)
arr[0] = 'c';
int _tmain(int argc, _TCHAR* argv[])
char str[] = "cry";
change(str);
printf("%s\\n", str);
return 0;
2.8 字符串
字符串string这个东西,事实上是不存在的,他是一串字符char组成的一个序列,比如java的String类其实用的就是c++的string,c++的string其实就是对字符功能做了一个封装
2.8.1 定义字符串的三种形式
char str1[] = 'C', 'R', 'Y';
char str2[] = "cry";
char* str3 = "cry";
2.8.2 字符串必须以[ \\0 ]结尾。如果不是,就会出现乱码
char str0[] = 'C', 'R', 'Y' ;
printf("%s\\n", str0);
char str1[] = 'C', 'R', 'Y','\\0' ;
printf("%s\\n", str1);
如果是以数组形式定义的字符串,一定要手动补'\\0'
,如果是char str2[] = "cry"; char* str3 = "cry";
这样,以指针或者直接""字符串的形式定义的字符串,c会直接为我们补上\\0
这个结尾,我们可以直接看他的内存中实际是怎么存的。
先打出这个数组的首地址:
char str0[] = 'C', 'R', 'Y' ;
printf("%p\\n", str0);
可以看到是0x00cffdf4
然后把他丢到vs的debug模式下,
可以看到在CRY
后面是cc,这个是内存中里没有分配的内存,在windows下默认初始化为cc,对应unicode码就是烫,突然有点怀念逝去的青春~
2.8.3 字符串的段异常
char str1[] = "cry";
char* str2 = "cry";
str1[0] = 'k';
*str2 = 'k';
0xC0000005
,这是个字符串使用中非常常见的异常。如果是以数组形式构建的字符串,是可以修改的。但如果是以指针形式构建,他就是一个字符串常量,只读不可改,试图修改他就会报字符串段异常。
0xC0000005
,这个在c++中有奇妙的使用,c++中的安全点就是通过这个技术暂停所有线程的,很好理解,段异常会通过中断被os捕获,捕获之后就可以调用安全点的停止;等中断结束后,再把安全点恢复,线程继续执行。
这个过程是不是很熟悉,jvm发生full gc的时候,会发生stw(stop the world),暂停所有的线程,等gc完毕再把线程重启,就是这个技术点。
2.8.4 字符串处理的头文件
下面的实例使用了上述的一些函数:
#include <stdio.h>
#include <string.h>
int main ()
char str1[14] = "runoob";
char str2[14] = "google";
char str3[14];
int len ;
/* 复制 str1 到 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\\n", str3 );
/* 连接 str1 和 str2 */
strcat( str1, str2);
printf("strcat( str1, str2): %s\\n", str1 );
/* 连接后,str1 的总长度 */
len = strlen(str1);
printf("strlen(str1) : %d\\n", len );
return 0;
可以在 C 标准库中找到更多字符串相关的函数。
更新不易,求求客官姥爷们留下尊贵的一键三连哈~
以上是关于3小时学会C语言横向对比/纵向剖析,轻松学习C语言的主要内容,如果未能解决你的问题,请参考以下文章
C语言学习笔记超级炫酷的C语言实用小技巧,学会这些隐藏技巧,早下班一小时