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位)

数据类型占用空间(字节)
char1
short2
int4
long4
float4
double8

📔 c:linux(这个和不同linux-os有关)

数据类型占用空间(字节)
char1
short2
int4
long8
float4
double8

可以通过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

数据类型占用空间(字节)
boolean1
byte1
char2
short2
int4
long8
float4
double8

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语言的主要内容,如果未能解决你的问题,请参考以下文章

3小时学会C语言横向对比/纵向剖析,轻松学习C语言

C语言学习笔记超级炫酷的C语言实用小技巧,学会这些隐藏技巧,早下班一小时

计算机二级要报名了。报哪个呢?报C语言吧,比office好考容易得多!C语言题库免费,1月学会

高级C语言目录

学习笔记C语言基础入门——这一篇就够了!

学习笔记C语言基础入门——这一篇就够了!