Java老兵 转C语言,需要学习的点(最易懂的解释)

2024-02-07 00:12

本文主要是介绍Java老兵 转C语言,需要学习的点(最易懂的解释),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、static

1.1 修饰函数内的局部变量:

void sayHi(void)
{              static int index = 5;index++;
}

多次调用sayHi函数,index = 5 只有在第一次调用的时候初始化一次,后面的多次调用,此句话就不执行了。

1.2 修饰全局变量或函数:

#include <stdio.h>static int count = 10; // 全局变量
static void write_eeprom(const U8* xdr); // 全局函数

当 static 修饰全局变量或函数时,会使之的作用域限制在声明它的文件内。
只有此文件内可以调用,外部文件不可访问。

二、extern

直译 外面的

extern int num;  // 声明了一个整型变量num,它是在外面的、其他的文件中定义的。

执行此句代码时,不会为 num 分配任何存储空间,只是指示编译器 num 是在其他文件中定义的。
extern也可以用来修饰函数。

extern void write();

三、局部变量、全局变量

全局变量保存在内存的全局存储区中,占用静态的存储单元;
局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

默认值:
当局部变量被定义时,系统不会对其初始化,它的默认值有可能是一个垃圾值。
定义全局变量时,系统会自动对其初始化:

数据类型初始化默认值
int0
char‘\0’
float0
double0
pointerNULL

最佳实践是,无论是局部变量还是全局变量,定义的时候最好给显式地赋值一个默认值。

四、数组名

int heightArray[] = {1, 3, 4, 9}

heightArray既是数组名,也是指向数组第一个元素地址(也是数组的地址)的指针。

int* ptr = &heightArray[0];
也可以这么写
int* ptr = heightArray;

下面是我写的一个小程序,它本身没什么意义,单纯为了使大家更好的理解,数组名是个指针是什么意思:

int sumPreTwo(int* ptr);int main() {int heightArray[] = {1, 3, 4, 9};int ret = sumPreTwo(heightArray);printf("sum pre two, the result is %d", ret);return 0;
}int sumPreTwo(int* ptr)
{int first = *ptr;int* secondPtr = ptr + 1; // line2int second = *secondPtr; // line3return first + second;
}

输出:sum pre two, the result is 4

当然 line2和line3可以合并为一句

int second = *(ptr + 1)

由此,引发出求和的另一种写法:

#include <stdio.h>int sum(int* ptr, int size);int main() {int heightArray[] = {1, 3, 4, 9};int ret = sum(heightArray, 4);printf("sum all, the result is %d", ret);return 0;
}int sum(int* array, int size)
{int sum = 0;for(int i=0; i<size; i++){sum += *(array+i);}return sum;
}
/
输出: sum all, the result is 17

它本身也没什么意义,也是为了帮助大家更好地理解,为什么说:数组名是一个指向数组首元素的指针

五、定义一个函数,返回值是一个数组

C 语言不允许返回一个完整的数组,只能返回一个指向数组的指针。

#include <stdio.h>int[] genArray(int size);int main() {int heightArray[] = genArray(3);for(int j=0; j<3; j++){printf("%d element is: %d", j, heightArray[j]);}return 0;
}int[] genArray(int size)
{int arr[size];for(int i=0; i<size; i++){arr[i] = i;}return arr;
}

像上面这样的代码,不好意思,C不支持,编译就报错:
在这里插入图片描述

#include <stdio.h>int* genArray();int main() {int* heightArrayPtr = genArray();for(int j=0; j<3; j++){printf("%d element is: %d \n", j, *(heightArrayPtr+j));}return 0;
}int* genArray()
{static int arr[3];for(int i=0; i<3; i++){arr[i] = i;}return arr;
}

六、静态数组

int staticArray[5]; // 静态数组声明
int staticArray[] = {1, 2, 3, 4, 5}; // 静态数组声明并初始化

获取数组长度:

int array[] = {1, 2, 3, 4, 5};
int length = sizeof(array) / sizeof(array[0]);

整个数组占用空间大小 / 一个元素占用空间大小 = 20字节 / 4字节 = 5个元素

七、动态数组(手动分配内存)

int size = 5;
int *dynamicArray = (int *)malloc(size * sizeof(int)); // 动态数组内存分配
// 使用动态数组
free(dynamicArray); // 动态数组内存释放

自己写了个例子:

#include <stdio.h>
#include <stdlib.h>int* genArray(int size);int main() {int size = 5;int* heightArrayPtr = genArray(size);for(int j=0; j<size; j++){printf("%d element is: %d \n", j, *(heightArrayPtr+j));}free(heightArrayPtr); // ※一定别忘了内存释放return 0;
}int* genArray(int size)
{int* array = (int*)malloc(size * sizeof(int));for(int i=0; i<size; i++){array[i] = i;}return array;
}
//
0 element is: 0
1 element is: 1
2 element is: 2
3 element is: 3
4 element is: 4

malloc 的参数是字节数,此函数在 stdlib.h 中声明。
分配内存的函数,还有calloc,它们的区别是:

  • malloc 只分配内存,并不初始化,默认值可能是一个垃圾值;
  • calloc 分配内存,并初始化值为0;
  • calloc 的参数是两个,元素数与每个元素占用的空间;
  • 而malloc只有一个占用空间字节数参数;

它们在 stdlib.h中的声明如下:

  void *__cdecl calloc(size_t _NumOfElements,size_t _SizeOfElements);void __cdecl free(void *_Memory);void *__cdecl malloc(size_t _Size);

八、枚举

enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
};
// 值 1 2 3 4 5 6 7
enum season {spring, summer=3, autumn, winter};
// 值 0 3 4 5

enum(枚举)类型的值只能是整数类型,包括int、short、char等。你不能直接在enum中指定浮点数(如float或double)作为其值。
如果想创建浮点型枚举,可以参考以下实现:

float floatValues[3] = {0.1, 0.2, 0.3};  
enum {  FLOAT_VALUE_1,  FLOAT_VALUE_2,  FLOAT_VALUE_3  
};

8.1 整数转换为枚举

#include <stdio.h>enum day
{saturday,sunday,monday
} workday;int main()
{int day1value = 1;enum day weekend;weekend = (enum day) day1value;  //类型转换// weekend = day1value; // 两种类型,不能直接赋值printf("weekend: %d", weekend);return 0;
}

九、空指针

int  *ptr = NULL; 

NULL 指向0x0系统保留的地址,应用程序不能访问。在程序中表达的意思是,此指针没有指向有效值(“没有初始化”)。

十、函数指针类型与变量

声明一个函数指针类型:

typedef int (*AddNum)(int);

typedef 表示我要定义一个类型。AddNum表示这个类型的名字。* 表示这是个函数指针类型。

int xxx(int)

这个范式,表达了定义的这个函数是什么样的函数,第一个int是返回值,第二个int是参数列表。xxx函数名是任意名,不是重点。
简单理解可以把它当成一个函数的模板,就是说我定义的这个函数指针类型,它指向的函数是一个怎样的函数:它的返回值是什么类型,它的参数列表是怎样的。
它定义了某种函数的模板,它代表了一堆这样的函数。

回顾一般指针的使用:

int a = 3;
int* ptr = &a;

要想定义一个指向整型的指针变量,首先要有整型这个类型(即 int),由于int 是C语言帮我们封装好了,所以可以直接用。
但是当要定义一个指向函数的指针变量时,并没有这个函数类型,所以才需要我们自己先声明一种函数类型。int xxx(int)这个范式就描述了这种函数类型。

写了个例子,便于加深理解:

#include <stdio.h>int addNum(int);
int ori = 0;
typedef int (*AddNum)(int); // 定义函数指针类型
void sayhello(AddNum);int main() {AddNum an = &addNum; // 创建一个函数指针变量,它指向了addNum函数printf("hello!! %d \n", an(10)); // 调用函数指针for(int i=0; i<3; i++){sayhello(an);}return 0;
}int addNum(int add)
{ori += add;return ori;
}void sayhello(AddNum addNum)
{printf("hello!! %d \n", addNum(1));
}

十一、共用体

union Data
{int i;float f;char  str[20];
};

Data中所有变量,共用一块内存地址。
i 占4字节,f 占4字节,str占20字节,所以Data变量占用空间为20字节(等于所有成员变量中占用空间最大的)。

int main( )
{union Data data;        data.i = 10;data.f = 220.5;strcpy( data.str, "C Programming");printf( "data.i : %d\n", data.i);printf( "data.f : %f\n", data.f);printf( "data.str : %s\n", data.str);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming

可以看到,共用体的 i 和 f 成员的值有损坏。
因为共用内存地址,当写 str 值的时候,20个字节全重写了一遍,所以i、f 的值被覆盖掉了。

十二、typedef vs #define

它们有以下几点不同:

typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

#define

在C语言中,#define是一个预处理指令,用于在编译之前进行文本替换。预处理器会在编译之前处理源代码,根据#define指令来替换代码中的文本。

例如,以下代码:

#define PI 3.14159

在编译之前,在代码中的任何地方出现的PI,它都会被预处理器替换为3.14159

除了简单的文本替换,#define还可以用于定义常量、宏函数等。例如:

#define SQUARE(x) ((x) * (x))

上述宏定义了一个名为SQUare的宏函数,该函数接受一个参数x并返回其平方。当你在代码中使用SQUare(5)时,它会被预处理器替换为((5) * (5))

typedef

在C语言中,typedef是一个关键字,它本身并不创建新的数据类型,只是为已有的数据类型创建了一个同义词或别名。

编译器在编译时解析typedef声明,并在符号表中记录这个新的类型名称与其对应的基础类型。这样,在后续的代码中,每当编译器遇到这个新的类型名称时,它都会用基础类型来替换它。

例如,考虑以下typedef声明:

typedef int Integer;
typedef struct {int x;int y;
} Point;

对于第一个typedef,编译器会记住Integerint的一个别名。在后续的代码中,任何使用Integer的地方都会被当作int来处理。

对于第二个typedef,编译器会创建一个结构体类型,并为它分配一个别名Point。这样,在代码中就可以使用Point来声明该结构体的变量,而不需要每次都写出完整的结构体定义。

需要注意的是,typedef的作用域是局部的,它遵循C语言的作用域规则。如果在一个函数内部使用typedef定义了一个类型别名,那么这个别名只在该函数内部有效。在函数外部,编译器将不认识这个别名,除非在外部也有相应的typedef声明。

十三、输入&输出

C 语言把所有的设备都当作文件
scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕)

#include <stdio.h>      // 标准io库
int main()
{printf("hello");return 0;
}

stdio.h 是一个头文件 (标准输入输出头文件) and #include 是一个预处理命令,用来引入头文件。 当编译器遇到 printf() 函数时,如果没有找到 stdio.h 头文件,会发生编译错误。

printf() %d 匹配整型,%f 匹配float,%p 匹配指针,%s匹配字符串,%c匹配字符

getchar() & putchar() 函数

getchar()读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符

gets() & puts() 函数

char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。

scanf() 和 printf() 函数

带格式的输入输出

#include <stdio.h>
int main( ) {char str[100];int i;printf( "Enter a value :");scanf("%s %d", str, &i);printf( "\nYou entered: %s %d ", str, i);printf("\n");return 0;
}

十四、文件读写

#include<stdio.h>int main()
{FILE *f = NULL;f = fopen("D:/2-Language/C/C-project1/tmp/test.txt", "w+");fprintf(f, "Hello World! \n");fputs("this is test for fputs...\n", f);fclose(f);
}
int fputc( int c, FILE *fp );  // 写一个字符
int fputs( const char *s, FILE *fp ); // 写一个字符串
int fprintf(FILE *fp,const char *format, ...)  // 把一个格式化字符串写入到文件中int fgetc( FILE * fp ); // 读单个字符
char *fgets( char *buf, int n, FILE *fp ); // 读n-1个字符到buf,直到换行或文件结尾
int fscanf(FILE *fp, const char *format, ...) // 读到第一个空格或换行为止

十五、预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。

#define 定义宏
#include 包含一个源代码文件

15.1 预定义宏

ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
__DATE__当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。
__TIME__当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。
__FILE__这会包含当前文件名,一个字符串常量。
__LINE__这会包含当前行号,一个十进制常量。

15.2 预处理器运算符

宏延续运算符(\)
字符串常量化运算符(#)

十六、头文件

A simple practice in C 或 C++ 程序中,建议把所有的
常量、宏、系统全局变量和函数原型
写在头文件中,在需要的时候随时引用这些头文件。

引用头文件相当于复制头文件的内容
引用头文件会在预编译阶段,进行处理。

16.1 只引用一次头文件

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

#ifndef HEADER_FILE
#define HEADER_FILEthe entire header file file#endif

这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。

十七、错误处理

C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。您可以在 errno.h 头文件中找到各种各样的错误代码。

所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。

  • perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
  • strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。
#include <stdio.h>
#include <errno.h>
#include <string.h>extern int errno ;int main ()
{FILE * pf;int errnum;pf = fopen ("unexist.txt", "rb");if (pf == NULL){errnum = errno;fprintf(stderr, "错误号: %d\n", errno);perror("通过 perror 输出错误");fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));}else{fclose (pf);}return 0;
}
///
错误号: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory

17.1 程序退出状态

exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);<stdlib.h>
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

十八、可变参数(不常用,了解下就行)

int func_name(int arg1, ...);
#include <stdio.h>  
#include <stdarg.h>  // 定义一个可变参数函数  
void print_numbers(int count, ...) {  va_list args; // 定义一个va_list类型的变量  va_start(args, count); // 初始化args,使其指向第一个可变参数  for (int i = 0; i < count; i++) {  int value = va_arg(args, int); // 获取下一个整数参数  printf("%d ", value);  }  va_end(args); // 清理va_list变量  
}  int main() {  print_numbers(3, 1, 2, 3); // 输出: 1 2 3  print_numbers(5, 4, 5, 6, 7, 8); // 输出: 4 5 6 7 8  return 0;  
}

使用va_list、va_start、va_arg和va_end宏来处理可变参数。这三个宏,在<stdarg.h>中定义。

十九、内存分配

序号函数描述
1void *calloc(int num, int size);在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。
2void free(void *address);该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3void *malloc(int num);在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4void *realloc(void *address, int newsize);该函数重新分配内存,把内存扩展到 newsize。

void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{char name[100];char *description;strcpy(name, "Zara Ali");/* 动态分配内存 */description = (char *)malloc( 30 * sizeof(char) );if( description == NULL ){fprintf(stderr, "Error - unable to allocate required memory\n");}else{strcpy( description, "Zara ali a DPS student.");}/* 假设您想要存储更大的描述信息 */description = (char *) realloc( description, 100 * sizeof(char) );if( description == NULL ){fprintf(stderr, "Error - unable to allocate required memory\n");}else{strcat( description, "She is in class 10th");}printf("Name = %s\n", name );printf("Description: %s\n", description );/* 使用 free() 函数释放内存 */free(description);
}
 输出:
Name = Zara Ali
Description: Zara ali a DPS student in class 10th
strcpy( description, "Zara ali a DPS student."); // copy
strcat( description, "She is in class 10th"); // 拼接

C 语言中常用的内存管理函数和运算符
malloc() 函数:用于动态分配内存。它接受一个参数,即需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。

free() 函数:用于释放先前分配的内存。它接受一个指向要释放内存的指针作为参数,并将该内存标记为未使用状态。

calloc() 函数:用于动态分配内存,并将其初始化为零。它接受两个参数,即需要分配的内存块数和每个内存块的大小(以字节为单位),并返回一个指向分配内存的指针。

realloc() 函数:用于重新分配内存。它接受两个参数,即一个先前分配的指针和一个新的内存大小,然后尝试重新调整先前分配的内存块的大小。如果调整成功,它将返回一个指向重新分配内存的指针,否则返回一个空指针。

sizeof 运算符:用于获取数据类型或变量的大小(以字节为单位)。

指针运算符:用于获取指针所指向的内存地址或变量的值。

& 运算符:用于获取变量的内存地址。

  • 运算符:用于获取指针所指向的变量的值。

-> 运算符:用于指针访问结构体成员,语法为 pointer->member,等价于 (*pointer).member。

memcpy() 函数:用于从源内存区域复制数据到目标内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。

memmove() 函数:类似于 memcpy() 函数,但它可以处理重叠的内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。

本文完。


觉得有用的话,动动你发财的小手手,点个赞,^(∩_∩)^ 谢谢~

这篇关于Java老兵 转C语言,需要学习的点(最易懂的解释)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/685971

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定