day10-c语言基础

四 C语言编译器安装

## 学习c语言的原因
一般公司的apk,基于Java实现的加密。
- jadx反编译java,分析代码
NB公司的的apk,基于Java+C语言实现加密(JNI开发)。
- jadx反编译java,分析代码
- ida反编译c语言,分析代码
## c语言动态链接库so文件反编译工具
在C语言中,生成的可执行文件或共享库(.so文件)可以被反汇编和反编译,以还原源代码或了解其实现细节。下面是一些常用的C语言so文件反编译工具:
IDA Pro:IDA Pro是一款强大的逆向工程工具,支持多种平台和架构,包括C语言。它提供了反汇编、反编译、调试等功能,能够帮助你还原C语言so文件的源代码。但需要注意的是,IDA Pro是商业软件,需要购买授权。
Ghidra:Ghidra是一款由美国国家安全局(NSA)开发的开源逆向工程工具。它支持多种平台和架构,并提供反汇编、反编译、脚本编写等功能,可以用于分析和还原C语言so文件的代码。
Radare2:Radare2是一个开源的逆向工程框架,提供了反汇编、反编译、调试等功能。它可以用于分析和还原C语言so文件的源代码,支持多种平台和架构。
Hopper Disassembler:Hopper Disassembler是一款反汇编工具,支持多种平台和架构。它可以将C语言so文件反汇编为汇编代码,并提供可视化界面和一些高级分析功能。

4.1 编译器安装

# C语言是编译型语言,编写好代码之后,需要由编译器编译后成不同平台的可执行文件才能执行,常见的编译器GCC、MSVC、Clang
# mac,linux: 默认自带gcc,不需要额外安卓,mac如果没有就去直接安装xcode。
xcode: https://developer.apple.com/xcode/
# win:MinGW
MInGW全称为:Minimalist GNU on Windows.将经典的开源C语言编译器GCC移植到了Windows平台下,并且包含了Win32API,因此可以将源代码编译为在Windows中运行的可执行程序
而且还可以使用一些Windows不具备的,Linux平台下的开发工具。概括来讲:MinGW 是GCC的Windows版本
# MinGW-w64与MinGW的区别:
MinGW只能编译生产32位可执行程序;
MinGW-w64可以编译成64bit或者32bit可执行程序
# MinGW-w64安装,参照教程
https://zhuanlan.zhihu.com/p/355510947
https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/
# 我们安装 MinGW
-下载:https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe/
-下载不成功,用这个地址:https://sourceforge.net/projects/mingw/files/latest/download
# 安装路径加入环境变量,测试
# 编写代码 main.c
# include <stdio.h>
int main(int argc, char const *argv[]) {
printf("hello world\n");
return 0;
}
# 编译并运行(在终端)
gcc main.c
a.exe

4.2 IDE安装

# 我们使用jetbrains公司的CLion作为IDE
#下载地址:(破解方案同pycharm和idea)
https://www.jetbrains.com/clion/download/other.html
# 一路下一步安装,成功后打开,创建项目

4.3 clion中文乱码问题

4.3.1 解决方案一

按住 Ctrl+Shift+Alt+/ 选中Registry...

image-20231021141732499

image-20231021141741349

4.3.2 解决方案二

image-20231021141750275

image-20231021141806236

五 基础语法

5.1 整形

类型 存储大小 值范围
char 1 字节 -128 到 127 或 0 到 255
unsigned char 1 字节 0 到 255
signed char 1 字节 -128 到 127
int 2 或 4 字节 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2 或 4 字节 0 到 65,535 或 0 到 4,294,967,295
short 2 字节 -32,768 到 32,767
unsigned short 2 字节 0 到 65,535
long 4 字节 -2,147,483,648 到 2,147,483,647
unsigned long 4 字节 0 到 4,294,967,295
#include <stdio.h>
int main() {
char i=99;
printf("%d\n",i);
printf("%c\n",i);
unsigned char i1=99;
printf("%d\n",i1);
printf("%c\n",i1);
//short i=32767; // 默认有符号,整数最大32767
//signed short i=32767; // 默认有符号,整数最大32767
unsigned short i1=65535; // 有符号,正数最大 65535
//int i2=65536;
long i3=65536;
printf("%d",i3);
// 各种类型的存储大小与系统位数有关,但目前通用的以64位系统为主
// int 在64位机器上,占4字节
int i9=9999;
printf("%d\n",sizeof(i9));
printf("%d\n",sizeof(int));
return 0;
}

5.2 浮点型

类型 存储大小 值范围 精度
float 4 字节 1.2E-38 到 3.4E+38 6 位有效位
double 8 字节 2.3E-308 到 1.7E+308 15 位有效位
long double 16 字节 3.4E-4932 到 1.1E+4932 19 位有效位
#include <stdio.h>
int main() {
printf("float 存储最大字节数 : %d \n", sizeof(float));
printf("double 存储最大字节数 : %d \n", sizeof(double));
printf("long double 存储最大字节数 : %d \n", sizeof(long double));
// %f代表一般计数法输出,%e代表指数计数法输出
float f1=365.123456789F;
printf("%f---%e\n",f1,f1);
double f2=365.123456789;
printf("%f---%e\n",f2,f2);
long double f3=365.123456789L;
printf("%Lf---%Le\n",f3,f3);
return 0;
}

5.3 常量

#include <stdio.h>
int main() {
const int MAX_VALUE =99;
//MAX_VALUE =199;
printf("%d\n",MAX_VALUE );
return 0;
}

5.4 运算符

  • 算术运算符

A 的值为 10,变量 B 的值为 20

运算符 描述 实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母 B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++ 自增运算符,整数值增加 1 A++ 将得到 11
-- 自减运算符,整数值减少 1 A-- 将得到 9
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
int c;
c = a + b;
printf("a+b结果是 %d\n", c);
c = a - b;
printf("a-b结果是 %d\n", c);
c = a * b;
printf("a * b 结果是%d\n", c);
c = b / a;
printf("b / a的值是 %d\n", c);
c = 10 % 3;
printf("10 % 3取整除的值是 %d\n", c);
c = a++; // 赋值后再加 1 ,c 为 10,a 为 11
printf("赋值后再加 的值是 %d,a的值为:%d\n", c, a);
c = a--; // 赋值后再减 1 ,c 为 11 ,a 为 10
printf("赋值后再减 1的值是 %d,a的值为:%d\n", c, a);
return 0;
}
// a++ 与 ++a 的区别
  • 关系运算符
运算符 描述
== 检查两个操作数的值是否相等,如果相等则条件为真。
!= 检查两个操作数的值是否相等,如果不相等则条件为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。
  • 逻辑运算符
运算符 描述
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。
#include <stdio.h>
#include <stdbool.h>
int main() {
bool isTrue = true;
int a = 10;
int b = 20;
if (a && b) {
printf("条件为真\n");
}
return 0;
}
  • 赋值运算符
运算符 描述
= 简单的赋值运算符,把右边操作数的值赋给左边操作数
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数
<<= 左移且赋值运算符
>>= 右移且赋值运算符
&= 按位与且赋值运算符
#include <stdio.h>
int main() {
int a = 21;
int c;
c += a;
printf("%d\n", c);
c *= a;
printf("%d\n", c);
return 0;
}
  • 其它
运算符 描述 实例
sizeof() 返回变量的大小。 sizeof(a) 将返回 4,其中 a 是整数。
& 返回变量的地址。 &a; 将给出变量的实际地址。
* 指向一个变量。 *a; 将指向一个变量。
? : 条件表达式 如果条件为真 ? 则值为 X : 否则值为 Y
#include <stdio.h>
#include <stdbool.h>
int main() {
int a = 4;
printf("%d\n", sizeof(a));
int *ptr = &a;
printf("a 的值是 %d,ptr的值是%p\n", a, ptr);
a = 10;
int b = (a == 1) ? 20 : 30;
printf("b 的值是 %d\n", b);
return 0;
}

5.5 if 判断

#include <stdio.h>
#include <stdbool.h>
int main() {
int num;
printf("输入一个数字 : ");
scanf("%d", &num);
if (num > 90) {
printf("优秀");
} else if (num > 60 && num < 90) {
printf("及格");
} else {
printf("不及格");
}
return 0;
}

5.6 循环

循环类型 描述
while 循环 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for 循环 多次执行一个语句序列,简化管理循环变量的代码。
do...while 循环 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
#include <stdio.h>
#include <stdbool.h>
int main() {
int num = 0;
// while循环
// while (num<10){
// printf("%d\n",num);
// num++;
// }
//do while 循环
// do {
// printf("%d\n", num);
// num++;
// } while (num < 10);
// for 循环
// for (int i = 0; i < 10; ++i) {
// printf("%d\n", i);
// }
//死循环
for (; ; ) {
printf("我是死循环");
};
return 0;
}

5.7 函数

返回值类型 函数名( 参数类型 形参 )
{
函数体;
}
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int a = add(10, 10);
printf("%d", a);
return 0;
}

5.8 字符和字符串

在C语言中没有字符串。

字符数组 创造出字符串出来(每个字符占1个字节)。

#include <stdio.h>
int main() {
// 定义字符
char a = 'a';
printf("%c\n", a);
// 定字符串
char s[]="justin";
char s1[]= {'j','u','s','t','i','n','\0'};
printf("%s\n", s);
printf("%s\n", s1);
return 0;
}

5.9 数组

对于数组来说,内部元素是挨个存放,内存地址相邻。

它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,是一系列相同类型的变量

char v2[]= {'j','u','s','t','i','n','\0'};
int v3[3] = {11,22,33};
  • 元素固定
  • 类型固定(每个元素在内存中占据长度相同)
#include <stdio.h>
int main() {
char v3[] = "justin";
printf("第0个位置值:%c,内存地址:%p \n", v3[0], &v3[0]);
printf("第1个位置值:%c,内存地址:%p \n", v3[1], &v3[1]);
printf("第2个位置值:%c,内存地址:%p \n", v3[2], &v3[2]);
//int a[3]; 定义没有初始化
//int a[3]={1,2,3}; // 定义并初始化
int a[]={1,2,3}; // 定义并初始化,大小可以不填,但是定义完后,大小也固定
printf("%d\n",a[0]);
printf("%d\n",a[1]);
printf("%d\n",a[2]);
// 数组大小
printf("%d",sizeof(a) / sizeof(a[0]));
return 0;
}
# include <stdio.h>
int main() {
int v3[] = {11, 22, 33, 44, 55, 66}; // 每个整型4字节
printf("第0个位置值:%d,内存地址:%p \n", v3[0], &v3[0]); // 值所在的内存地址 0x00000
printf("第1个位置值:%d,内存地址:%p \n", v3[1], &v3[1]); // 0x00004
printf("第2个位置值:%d,内存地址:%p \n", v3[2], &v3[2]); // 0x00008
return 0;
}

六 指针

指针是存储变量内存地址的变量

image-20231021141925517

6.1 定义指针类型变量和取变量地址

#include <stdio.h>
int main() {
int v1 = 666;
int *v2 = &v1; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
printf("v2的值为:%p", v2);
return 0;
}

6.2 指针类型变量解引用

#include <stdio.h>
int main() {
int v1 = 666;
int *v2 = &v1; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
printf("v2的值为:%p\n", v2);
printf("v2解引用后:%d\n",*v2);
return 0;
}

6.3 修改变量的值

#include <stdio.h>
int main() {
int v1 = 666;
int *v2 = &v1; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
printf("v2的值为:%p\n", v2);
printf("v2解引用后:%d\n",*v2);
// 修改v1的值,查看v2
v1 = 999;
printf("v1的值为:%p\n", v1);
printf("v2的值为:%p\n", v2);
printf("v2解引用后:%d\n",*v2);
return 0;
}

6.4 指针的零值和长度

#include <stdio.h>
int main() {
//int *v2; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
//char *v2; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
//long *v2; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
long *v2 = NULL; // & 取地址符,得到是 指针类型(64位操作系统,8字节)
printf("%p\n", v2);
printf("%d\n", sizeof(v2));
if (!v2) {
printf("指针为空");
}
return 0;
}

6.5 通过指针修改原变量值

#include <stdio.h>
int main() {
int a=10;
int *v2 = &a;
*v2=99;
printf("a的值为:%d\n",a);
printf("v2的值为:%p\n",v2);
return 0;
}

6.6 指针类型参数

#include <stdio.h>
void changPointA(int *p) {
*p = 99;
}
void changA(int a) {
a = 99;
}
int main() {
int a = 10;
int *v2 = &a;
printf("v2的值为:%p\n", v2);
// changPointA(v2); //传递到函数中,a的值被改变了
changA(a); //传递到函数中,a的值没有被改变
printf("v2的值为:%p\n", v2);
printf("a的值为:%d\n", a);
return 0;
}

6.7 数组的指针(指针运算)

#include <stdio.h>
int main() {
int a[3] = {11, 22, 33};
int *p = &a;
printf("数组的指针值为:%p\n", p);
printf("取数组第0个元素方式一:%d\n",a[0]);
printf("取数组第0个元素方式二:%d\n",*p);
printf("取数组第1个元素方式一:%d\n",a[1]);
printf("取数组第1个元素方式二:%d\n",*(++p));
return 0;
}
// 指针数组和数组指针
#include <stdio.h>
#include <string.h>
int main() {
int a[3] = {11, 22, 33};
int *p = &a;
printf("数组的指针:%p\n", p);
int i = 10;
int x = 20;
int y = 30;
int *p1[] = {&i, &x, &y};
printf("指针数组:%d", **p1);
return 0;
}

6.8 指针的指针

#include <stdio.h>
int main() {
int a = 100;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
printf("p3的值为:%p\n", p3);
printf("p2的值为:%p\n", *p3);
printf("p1的值为:%p\n", **p3);
printf("a的值为:%d\n", ***p3);
return 0;
}

6.9 字符串案例

字符串格式化

#include <stdio.h>
int main() {
char a[6];
char *p = &a;
// 格式化
sprintf(p, "%c", 'j');
p += 1;
sprintf(p, "%c", 'u');
p += 1;
sprintf(p, "%c", 's');
p += 1;
sprintf(p, "%c", 't');
p += 1;
sprintf(p, "%c", 'i');
p += 1;
sprintf(p, "%c", 'n');
printf("值为:%s", a);
return 0;
}

判断字符串包含关系

#include <stdio.h>
#include <string.h>
int main() {
char name[] = "justin is handsome";
// 判断是name中是否存在子序列is
char *res = strstr(name, "is");
if (!res) {
printf("不存在");
} else {
printf("存在,从位置 %p 匹配成功的\n", res);
}
return 0;
}

字符串相加-复制字符串

#include <stdio.h>
#include <string.h>
#include<stdlib.h>
int main() {
char name[]="justin";
char role[]="teacher";
char *s = malloc(strlen(name) + strlen(role) + 1); // 申请一个这么大的指针
strcpy(s, name); //字符串复制,把name内容复制到s指针上
strcat(s, role); // 字符串拼接,把s指针后面追加role字符串
printf(s);
return 0;
}
序号 函数 & 作用
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的长度。
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

七 结构体

一种用户自定义的数据类型,它允许存储不同类型的数据项

结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等

7.1 基本使用

#include <stdio.h>
#include <string.h>
#include<stdlib.h>
// 定义结构体
struct Person {
char name[30];
int age;
};
int main() {
// 使用结构体
struct Person p1 = {"justin", 18};
struct Person p2 = {"彭于晏", 18};
struct Person p3 = {"古天乐", 18};
printf("p1的名字为:%s\n", p1.name);
printf("p2的名字为:%s\n", p2.name);
printf("p3的年龄为:%d\n", p1.age);
// 结构体指针
struct Person *pointPerson =&p3;
printf("p3的年龄为:%d\n", (*pointPerson).age);
printf("p3的姓名为:%s\n", pointPerson->name);
return 0;
}

7.1 单向链表

image-20231021142018479

#include <stdio.h>
struct Node {
int data;
struct Node *next;
};
int main() {
struct Node v3 = {33};
struct Node v2 = {22, &v3};
struct Node v1 = {11, &v2};
printf("v1的数据为:%d\n", v1.data); // 11
printf("v1的下一个元素数据为:%d\n", v1.next->data); // 22
printf("v1的下一个元素的下一个数据为:%d\n", v1.next->next->data); // 33
return 0;
}

7.3 双向链表

image-20231021142025229

image-20231021142032696

#include <stdio.h>
struct Person
{
int data;
struct Person *next;
struct Person *prev;
};
int main()
{
struct Person p3 = { 33 };
struct Person p2 = { 22 };
struct Person p1 = { 11 };
p1.next = &p2;
p2.next = &p3;
p2.prev = &p1;
p3.prev = &p2;
printf("p1的值: %d\n", p1.data);
printf("p2的值: %d\n", p1.next->data);
printf("p3的值: %d\n", p1.next->next->data);
printf("p3的值: %d\n", p3.data);
printf("p2的值: %d\n", p3.prev->data);
printf("p1的值: %d\n", p3.prev->prev->data);
return 0;
}

7.4 双向环状链表

image-20231021142040501

# include <stdio.h>
struct Person
{
int data;
struct Person *next;
struct Person *prev;
};
int main()
{
struct Person p3 = { 33 };
struct Person p2 = { 22 };
struct Person p1 = { 11 };
p1.next = &p2;
p1.prev = &p3;
p2.next = &p3;
p2.prev = &p1;
p3.prev = &p2;
p3.next = &p1;
printf("p1的值: %d\n", p1.data);
printf("p2的值: %d\n", p1.next->data);
printf("p3的值: %d\n", p1.next->next->data);
printf("p1的值: %d\n", p1.next->next->next->data);
printf("p2的值: %d\n", p1.next->next->next->next->data);
printf("p3的值: %d\n", p1.next->next->next->next->next->data);
return 0;
}

八 预处理和头文件

8.1 预处理

预处理,在程序编译之前会先运行的。

# include <stdio.h>
# define ME 200
# define SIZE 18
int main() {
int data = 19;
//ME=99; // 是常量,不能修改
printf("%d-%d-%d \n", ME, SIZE, data);
return 0;
}
# include <stdio.h>
# define ADD(x1, x2)(x1+x2+100)
int main() {
int data = ADD(11, 22);
printf("结果:%d \n", data);
return 0;
}
# include <stdio.h>
# define DB(x1, x2)(x1*x2)
int main() {
int data = DB(2 + 1, 4);
printf("结果:%d \n", data); // 结果是6,由于先运行,变成了2+1*4
return 0;
}

8.2 头文件

项目目录
├── main.c
├── utils.c
└── utils.h
// utils.h
int add(int a,int b);
// utils.c
int add(int a,int b){
return a+b;
}
//main.c
# include <stdio.h>
# include "utils.h"
int main() {
int data = add(100,200);
printf("结果:%d \n", data);
return 0;
}

比如后期,我们要进行JNI开发时,我们会在自己的c文件用去引入C语言中提供、JNI中提供一些头文件,才能使用头文件中的功能。

8.3 Python源码中头文件的使用

https://www.python.org/downloads/source/
# github上cpython源码:https://github.com/python/cpython
#Objects 下会有咱们得列表,字典等结构体
https://github.com/python/cpython/tree/main/Objects#
# 列表:的源代码
https://github.com/python/cpython/blob/main/Objects/listobject.c
# 列表的头文件
https://github.com/python/cpython/blob/main/Include/listobject.h
posted @   hanfe1  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
历史上的今天:
2021-10-21 Linux的bg和fg命令
点击右上角即可分享
微信分享提示