前言1.前置知识
1.1 序1.2 编译器和解释器 2.C语言技术点及应用技巧
2.1 调试用IDE2.2 数据类型
2.2.1 c2.2.2 java 2.3 运算符2.4 条件判断2.5 循环2.6 函数
2.6.1 函数的基本使用2.6.2 函数的可变参数:va_list2.6.3 函数的入参2.6.4 输出打印 2.7 数组
2.7.1 基本使用2.7.2 如何获得数组元素个数2.7.3 数组作为函数参数 2.8 字符串
2.8.1 定义字符串的三种形式2.8.2 字符串必须以[ ]结尾。如果不是,就会出现乱码2.8.3 字符串的段异常2.8.4 字符串处理的头文件
前言 沒有前言
1.前置知识 1.1 序计算机实际上是由底层一系列的数字电路实现的,通过输入电平的高(1)和低(0)和寄存器之间的组织关系来决定输出的结果,这个过程就是计算器的计算能力。
小学2年级的时候老师教过我们,最开始计算机是通过打孔来实现变成的,比如打了孔就代表输入1,不打孔就输入0,但是这样很麻烦,后来为了提高开发者的开发效率,就封装了一些具体的功能例如:加减乘除,映射到某些语句,只需要输入这些语句,底层的数字电路就会去实现对应的功能,汇编语言就诞生了。
但是汇编语言虽然比机器语言好用了很多,但是还是很麻烦,之后开发者们开发出了一系列语言,其中C语言就是比较火热的高级语言之一,在C的基础上又衍生出了C++,之后为了更便于开发者们的上手以及考虑到不同环境的隔离,又衍生出了Java,Python这些抽象程度更高的语言。
会出现上面这些语言迭代的根本原因,本质上是因为编译器越来越智能了,功能越来越强大,能够更加"人性化、智能化"地把我们写的高级语言最终翻译成底层的0/1机器码,其中编译又可以分为静态编译和动态编译。
静态编译:
所谓静态编译,就是把代码编译完成0/1机器码后再去运行,比如c语言的gcc编译器,c++的g++,都是基于这个原理。
动态编译:
所谓动态编译通过存在于解释型语言中,就是在代码运行过程中,解释器发现某段代码运行的特别频繁,他就会直接把这段代码做一个"运行时编译",直接编译成机器码,之后访问这段代码就直接执行,而不会走解释器的逻辑。
编译:
写到这里感觉还是有必要解释一下编译和解释的区别,因为有些小伙伴对这个概念的理解不够精准。通俗易懂的说,编译就是一段代码在运行前,要先编译成机器码,然后运行时会对这段机器码进行一个直接的执行,例如windows下的.exe程序。但是这种做法显然会受到操作系统环境的影响,所以比较典型的编译型语言有:c/c++,有些语言他比较"智能"(虽然其实也很笨了),他兼具了这种特性,其实他是一种解释性语言,但是在运行时会动态判断当前运行的代码是不是很"热",如果很"热"就给他"加个缓存",这个就是动态编译,比较典型的就是java中的JIT动态编译器。
解释:
比较典型的有python、java,都是这一类,解释器会直接把我们手敲的源代码直接解析成中间的字节码,然后在运行时把字节码解释成机器码进行执行。这种因为有个中间态,所以运行效率相对纯粹的编译型语言会低一点,可以通过兼容动态编译的方式进行优化。其中python的解释器会因为运行平台的不同而不同,java的解释器由jvm虚拟机决定。
运行效率:
2.C语言技术点及应用技巧静态编译 约等于 动态编译 > jvm模板解释器 > 解释器
因为博主本身对java比较熟悉,我会尽量对比着java和c的差别来讲解c语言,如果有其他语言比较熟悉的小伙伴也可以对比着来复习c语言,会有一些不一样的体会。
本文章涉及的C内容对于指针暂先不详细讲解,但是会用到也会提到。详细讲解我会再开一章,因为这个确实很重要。还有对于结构体、共用体等边边角角的部分,我也会放到下一篇讲解。下面开始进入正题(偶尔涉及的一些奇奇怪怪的知识,有兴趣的可以自行了解)。
讲解顺序:
2.1 调试用IDE数据结构
运算符
循环结构
条件判断
函数
数组
字符串
windows: visual studio,clion
linux: clion
2.2 数据类型 2.2.1 c勤劳是中华民族的传统美德,这边大家自行通过搜索引擎下载一下即可,简单了解下使用就可以上手了。博主windows下用的是vs2013,毕竟微软亲儿子性能肯定会更高一点
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型变量的长度: %dn", 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("%dn", a);
unsigned char b = 0xff;
printf("%dn", 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 |
不同语言之间,运算符这一块基本可以认为是相同的,没有太大差异。
算术运算符:
关系运算符:
逻辑运算符
位运算符
赋值运算符
其他不常用的遇到了再去搜索一下。
不同语言之间,基本相同。
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);
}
例如:
#includeint main () { int a = 10; if( a < 20 ) { printf("a 小于 20n" ); } printf("a 的值是 %dn", a); return 0; }
输出:
a 小于 20 a 的值是 102.5 循环
不同语言之间,基本相同。
while:先判断后执行
while(condition)
{
statement(s);
}
do while:先执行后判断
do
{
statement(s);
}while( condition );
for循环:
for ( init; condition; increment )
{
statement(s);
}
例如:
#include2.6 函数 2.6.1 函数的基本使用int main () { for( int a = 10; a < 20; a = a + 1 ) { printf("a 的值: %dn", a); } return 0; }
函数定义格式 ,跟Java一样的
声明与定义:可以分开,也可以不分开
c语言中的函数如果不声明的话,必须按顺序写,调用;否则就必须要函数声明
int t1(){
return t2();
}
int t2(){
return 10;
}
如果不写函数声明,因为t2()的定义在t1()之后,函数调用会报错,在开头前加一个函数声明即可int t2();
java中因为他用到了c++的动态绑定,所以不存在函数调用的顺序问题。
2.6.2 函数的可变参数:va_listVA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。
用法实例:
#includeint 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 = y;
y = temp;
return;
}
现在,让我们通过传递实际参数来调用函数 swap():
#includevoid swap(int x, int y); int main () { int a = 100; int b = 200; printf("交换前,a 的值: %dn", a ); printf("交换前,b 的值: %dn", b ); swap(a, b); printf("交换后,a 的值: %dn", a ); printf("交换后,b 的值: %dn", b ); return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
交换前,a 的值: 100 交换前,b 的值: 200 交换后,a 的值: 100 交换后,b 的值: 200
引用调用
通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
return;
}
现在,让我们通过引用传值来调用函数 swap():
#includevoid swap(int *x, int *y); int main () { int a = 100; int b = 200; printf("交换前,a 的值: %dn", a ); printf("交换前,b 的值: %dn", b ); swap(&a, &b); printf("交换后,a 的值: %dn", a ); printf("交换后,b 的值: %dn", b ); return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
交换前,a 的值: 100 交换前,b 的值: 200 交换后,a 的值: 200 交换后,b 的值: 100
与java的比较:
2.6.4 输出打印很显然可以看入参如果传入的是某个变量的地址&,也就相当于变成了java中的引用数据类型,这也是c语言操作基本数据类型的一个灵活之处。
c语言一般用printf函数,c++一般用cout。但是cout没有printf好用
#includeint main() { printf("hello worldn"); 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 };
也可以不指定初始值,不指定的时候就是初始元素个数
举例:
#includeint main () { int n[ 10 ]; int i,j; for ( i = 0; i < 10; i++ ) { n[ i ] = i + 100; } for (j = 0; j < 10; j++ ) { printf("Element[%d] = %dn", 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("%sn", 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 字符串必须以[ ]结尾。如果不是,就会出现乱码
char str0[] = { 'C', 'R', 'Y' };
printf("%sn", str0);
char str1[] = { 'C', 'R', 'Y',' ' };
printf("%sn", str1);
如果是以数组形式定义的字符串,一定要手动补' ',如果是char str2[] = "cry"; char* str3 = "cry";这样,以指针或者直接""字符串的形式定义的字符串,c会直接为我们补上 这个结尾,我们可以直接看他的内存中实际是怎么存的。
先打出这个数组的首地址:
char str0[] = { 'C', 'R', 'Y' };
printf("%pn", str0);
可以看到是0x00cffdf4
然后把他丢到vs的debug模式下,
可以看到在CRY后面是cc,这个是内存中里没有分配的内存,在windows下默认初始化为cc,对应unicode码就是烫,突然有点怀念逝去的青春~
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#include int main () { char str1[14] = "runoob"; char str2[14] = "google"; char str3[14]; int len ; strcpy(str3, str1); printf("strcpy( str3, str1) : %sn", str3 ); strcat( str1, str2); printf("strcat( str1, str2): %sn", str1 ); len = strlen(str1); printf("strlen(str1) : %dn", len ); return 0; }
可以在 C 标准库中找到更多字符串相关的函数。
更新不易,求求客官姥爷们留下尊贵的一键三连哈~



