从java转c语言

在专升本之前,以前的学校计算机学习主要是应用型围绕着Java来开展课程的,期间也做了不少Maven+SSM开发的项目,并能在实训课上独立开发这样的项目,但在专升本后,步入本科学习阶段,不管是学习数据结构课程,还是为以后考研做准备,因为考研专业课算法题很多都强调用C,C++语言开发,所以为了快速从Java转C语言,这里我就记录下其中主要差别,以及着重要点.

小知识:

C语言通常通过return返回函数运行结果(true,false),而通过指针参数返回结果数据
一般来讲,当对象为指针时需用“->”进行成员的访问,对象为普通时用“.”访问和赋值。

.cpp文件后缀也为c++文件
如果是c文件用.c后缀

stdio 就是指 “standard input & output"(标准输入输出)

所以,源代码中如用到标准输入输出函数时,就要包含这个头文件!

例如c语言中的 printf("%d",i); scanf("%d",&i);等函数。

引用方法

#include <stdio.h>

struct,typedef 和define等关键字

结构体(struct)

实际问题中,一组数据往往有很多种不同的数据类型,结构体可以有效的解决这个问题,在Java中,结构体即是实体类的体现.
结构体本质上还是一种数据类型,但它可以包括若干个"成员".
优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是"高内聚,低耦合"的,更利于程序的阅读理解,移植,而且结构体的存储方式可以提高CPU对内存的访问速度.

//一般形式
struct 结构名{
成员列表;
}
定义结构体变量:struct 结构名 结构体变量名;

也可以这样:
struct 结构名{
成员列表;} 结构体变量名;

还可以匿名结构体:
struct{
成员列表;}结构体变量名;
这里因为没有结构名,注意,就不能再次定义新的结构体变量了.

访问结构成员: 结构体变量.成员名;

结构体里为什么不能定义自己,而只能定义自己的指针
如果定义自己,会形成嵌套型循环,这对系统不友好,在不确定分配空间为多大时,使用指针会比较好

Java中的class对象体现的就是struct结构体 的思想

define

#define MAXSIZE 20

定义常量,c预处理器,编译时程序中所有的MAXSIZE都会被替换成20,这一过程被称为编译时替换
const限定变量为只读状态,即不可被修改

typedef

为一种数据类型定义一个新名字
常与struct联合使用

//二叉树链式存储结构
typedef struct node {
    char data;
    struct node* lchild, * rchild;
}BiTNode, *BiTree;

此结构体就声明了两种数据类型 BiTNode,和其指针BiTree
BiTNode=typedef struct node BiTNode;
即定义BiTNode为struct node 数据类型的别名.
声明变量时就可以:BiTNode bitree;

BiTree=typedef struct node* BiTree;
即定义BiTree为struct node* 数据类型的别名.
声明变量时就可以:BiTree bitrees;

常用函数库

常用的有
#include<stdlib.h> //动态分配函数和随机函数

#include<stdio.h>//输入输出
如果不引入库,将导致其函数不可用,分配内存空间无效.

指针

Java中没有显示的使用指针,开发者试图隐藏指针交由系统处理,实际上,一个对象的访问就是通过指针来实现的,一个对象会从实际的存储空间的某个位置开始占据一定的存储体,该对象的指针就是一个保存了对象的存储地址的变量,并且这个存储地址就是对象在存储空间中的起始地址,在许多高级语言中,指针是一种数据类型,在Java中是使用对象的引用来替代的.

从根本上看,指针是一个值为内存地址的变量(或数据对象),那么为什么声明指针不是 :pointer i呢?
C Primer Plus 书中给出了这样的解释:
因为声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作
要求知道操作对象的大小.另外,程序必须知道存储在指定地址上的数据类型…
类型说明符表明了指针所指向对象的类型,星号表明声明的变量是一个指针
在许多语言中,地址都归计算机管,对程序员隐藏,然而在c中,可以通过&运算符访问地址,通过
运算符获得地址上的值.

指针声明 数据类型 * 指针名;
获取指针指向的变量的值,即指针保存变量存储的地址,而我们要获取该变量的值.用 😗 指针名;

二级指针

即是保存指针地址的指针.通常会在用二级指针与函数参数结合使用,以返回指针,或承接malloc()分配产生的指针地址.
比如:

//获取栈顶成员的地址,通过栈指针获取栈地址位置,然后通过二级指针返回其成员地址
//这里由于栈内保存的是指针,其本身指针变量是有存储地址的,而指针变量的值存储的是树节点地址,
//而我们要保存的是指针变量本身的存储地址,而不是树节点的地址,这就需要(树节点的指针)的类型指针,也就是二级指针
int GetTop(SqStackS S, ElemType* x) {
	if (S->top == -1)
		return 0;
	*x = S->data[S->top];
	return 1;
}

malloc()

malloc()是动态分配内存空间,返回值是一个指针,且需要强转,我们一般应用在对数据类型初始化时
使用malloc函数,如果分配成功则返回指向被分配内存的指针,而不是其数据类型值的地址.
如:

int InitStack(SqStackS *S) {
	*S = (SqStackS)malloc(sizeof(SqStack));
    (*S)->top = -1;
	return 1;
}

这里是对栈的一个初始化函数,SqStackS即是栈指针.这里S就是一个二级指针.一级指针保存的是栈类型值地址,而二级指针保存的是栈指针类型的地址.

模块化开发

在Java开发中,我们通常会写很多的类,而c语言也可以实现模块化开发.

到目前为止,我们编写的大部分C语言程序都只包含一个源文件,没有将代码分散到多个模块中,对于只有几百行的小程序来说这或许可以接受,但对于动辄上万行的大中型程序,将所有代码都集中在一个源文件中简直是一场灾难,后续阅读和维护都将称为棘手的问题。

在C语言中,我们可以将一个.c文件称为一个模块(Module);所谓模块化开发,是指一个程序包含了多个源文件(.c 文件)以及头文件(.h 文件)。

C语言代码要经过编译和链接才能生成可执行程序:

编译是针对单个源文件(.c 文件)的,有多少个源文件就生成多少个目标文件,并且在生成过程中不受其他源文件的影响。也就是说,每个源文件都是独立编译的。

链接器的作用就是将这些目标文件拼装成一个可执行程序,并为代码(函数)和数据(变量、字符串等)分配好虚拟地址,这和搭积木的过程有点类似。

java c 转化 java转c语言_java


.h文件的引入,也可以理解为自定义函数库.

其中.h为头文件,一般定义数据类型,然后其相关.c文件引入.h文件并实现其内定义的函数.

然后有main方法的主文件只要引入.h文件即可.

值传递和地址传递

指针是一个保存地址的变量. 所以它也有自己的空间地址. 故指针的地址,和指针的值是两个不同概念.
值传递,顾名思义,就只是将值传递过去,那边的函数参数开辟新的空间,并复刻了其值
当值为普通变量时,传值到函数中,函数内对变量的操作不会影响函数外的变量
当值为普通变量时,传地址到函数中,函数通过指针接收其变量地址,对其值的操作,会影响到函数外的变量.
但当指针指向的地址发生变化,比如malloc()函数赋值给指针,或者接收新的指针,就会导致指针指向的地址发生变化,此时函数内的指针存储地址与函数外变量的地址不是同一内存空间,函数内的操作,就不会影响到函数外.

当值为指针变量时,传值到函数中,此时的值是变量的地址,函数同样通过指针接收其地址,此时函数内对变量的操作是作用在同一内存空间的,会对函数外的变量造成影响,
当值为指针变量时,传次指针变量的地址过去,函数通过二级指针接收其指针的地址.二级指针变量的值就是传过来的指针的地址.如果对其操作,比如重新分配内存空间,或者接收新的指针,那么其指针指向的地址就会发生变化,外面的指针指向的地址也会受到影响.

例子:

#include<stdio.h>//输入输出
#include<stdlib.h> //动态分配函数和随机函数

int*  init(int ** c) {
	int d = 7;
	//二级指针接收指针b的地址,*c即二级指针指代对象,
//即一级指针b,而指针b中其值是a的地址,所以打印a的地址
	printf("*c=%p\n", *c);  
	//将指针b的值做了改变,存储的为d的地址
	*c = &d;   
    //其打印值为指针b的值即,d的地址
	printf("*c=%p\n", *c);
	return *c;  //return 指针b
}

int main() {
	int a = 6;
	int* b= &a;//指针b 其值为a的地址, *b则指代所指向对象的值,即a
	printf("变量a的地址:&a=%p\n", &a); //% p是打印地址的
	printf("指针b所指代对象的值:*b=%d\n", *b);//输出所指代对象的值
	printf("指针b所存出的值,即a的地址:b=%p\n", b);  //以地址的方式输出指针的值即,a的地址
	printf("指针b本身存储地址:&b=%p\n",&b);  //输出指针b本身的地址
	int *e=init(&b);    //此时初始化指针e接收返回值,即与指针b同地址
	printf("*b=%d\n", *b);   //显然b所指代对象的值发生改变
	printf("&b=%p\n", &b);//输出指针b本身的地址
	*b = 10;
	printf("*b=%d\n", *b);
	printf("*e=%d\n", *e);
	return 0;
}

java c 转化 java转c语言_指针_02


从结果上看:

1.打印变量a的地址

2.打印指针b所指代对象的值

3.打印指针b的值,即指代对象a的地址

4.打印指针b本身的地址

5.二级指针c其值为指针b的地址 ,c即为所指代对象:指针b,而c的值即,指针b的值为变量a的地址

6.*c即指针b的值发生变化,存储d的地址

7.初始化指针e,由于指针b所指代对象发生改变,所以对应指代对象的值应为d的值7

8.而指针b本身的地址不变

9.e和b指针同存储空间地址,所以其一发生变化,两者输出值都发生改变

指针在函数中的应用:
通过指针接收变量的地址,

如果说普通变量有属性: 属性值,变量地址
那么指针变量就有: 属性值->变量地址->变量值 , 指针变量地址

二级指针变量就有: 属性值->指针变量地址->变量地址->变量值 , 二级指针变量地址

指针在函数中的作用

#include<stdio.h>
//指针在函数中的应用

int Test(int t) {
	t = 1;
	return t;
}

int  Test_2(int * t) {
	*t = 2;
	return 0;
}

int Test_3(int** t) {
	int m = 5;
	
	*t = &m;
}
int main(void) {
	int i = 0;
	int* i_2 = &i;
	i=Test(i);
	printf("test(i)=%d\n", i);
	Test_2(i_2);
	printf("test_2(i_2)=%d\n", i);
	Test_3(&i_2);
	printf("*i_2=%d\n", *i_2);
	return 0;
}

java c 转化 java转c语言_指针变量_03


当函数内是变量时,可以通过一级指针在函数外修改变量值,以影响函数内变量的值

当函数内是一级指针时,可以通过二级指针在函数外修改一级指针所指代对象的变量值。

char类型和字符串

c语言中char以及字符串:
表示字符型数组a中可以存放2个字符,第1个字符用a[0]访问,第2个字符用a[1]访问,最大下标可以用0~(2-1)范围的。比如a[100]合法下标范围是0~99;
当a需要保存字符串时,需要注意,字符串必须以0值结尾,表示成字符就是’\0’,而且这个0不算在字符串中的字符,那么你用a数组最多只能保存n-1个字符组成
的数组,如果是char a[2];的话只能保存一个字符组成的字符串;如:char a[20]={ “Hello !” };或者char a[20]={ ‘H’,‘e’,‘l’,‘l’,‘o’,’\0’ };这时字符串占
用6个数组元素,但字符串长度为5,如果你用strlen语句计算的长度也为5,你最多可在这个数组中保存长度为19的字符串,需要自己在末尾添加0或’\0’,前面语句
char a[20]={ “Hello !” };是编译器自动帮你加了结尾符0;
当懒得数字符串中字符个数时,也可以让编译器帮你数:char a[]={“Hello”};这与你自己写:char a[6]={ “Hello” };是一样的。
char a[2];
这是声明。声明变量 a 是 char 型数组,有2个元素。
语句里 写 a[0] 表示它是 char 型数组a 里的 第一个 元素
a[1] 是 char 型数组a 里的 第二个 元素。
char a[2];
也可以看成 是 字符串 变量 a。 由于 字符串要用1个单元存放字符串 结束符,所以只能 存放 长度为 1 的字符串。
声明,带初始化写法:
char a[2]={ ‘A’, ‘x’}; 初始化 a[0]=‘A’; a[1]=‘x’; – 单引号括起的是 字符常量
char a[2]=“A”; 初始化 字符串 “A” – 双引号括起的是 字符串,含 字符串结束符。