NDK学习笔记-C语言

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cj5785/article/details/89048638

本文简要回顾了C语言的一些注意事项和理解细节,不再赘述C语言的所有语法

头文件

头文件作为引入文件,在编译的时候,加载到源代码,参与编译
在VS2013中可以看到,当引入头文件时候,只能看到函数的声明,其实现是在编译时候查找的
C的动态库函数不可重名,而C++可以,这是因为C++有命名空间的存在,而C没有

//引入头文件
#include <stdio.h>
void main()
{
    printf("%s", "test");
}

基本数据类型

C中的基本数据类型包括:int, char, short, long, float, double
值得注意的是,在C语言中,char代表一个字节,而在Java中,char代表两个字节,在Java中与C的char对应的基本数据类型为byte,这个数据类型在C中是不存在的
基本数据类型常用的打印标识(与上述基本类型一一对应):%d, %c, %d, %ld, %f, %lf
%x – 十六进制小写
%X – 十六进制大写
%o – 八进制
%s – 字符串

sizeof关键字

在C语言中,sizeof作为关键字存在,而不是一个函数
使用sizeof()可以获得一个数据类型所占字节数的大小,例如:sizeof(char)

循环的注意事项

for循环的初始化值一般要在循环外定义,这样的原因是因为C89的规范,在很多发行版的Linux中,仍然采用的是C89的C语言规范,以下语句在很多Linux发行版无法编译通过

for(int i = 0; i < 10; i++){}

VS中使用scanf,gets等函数的注意事项

在VS中,像scanf,gets这样的函数被定义为不安全函数
要使用这些函数,就需要将其编译错误去掉,常用的方法有三种

  1. 添加宏定义,生效于单个文件
#define _CRT_SECURE_NO_WARNINGS
  1. 在设置中去除,生效于整个项目
    项目->属性->配置属性->C/C++ -> 预处理器 -> 预处理器定义,增加:_CRT_SECURE_NO_DEPRECATE
  2. 去除编译时的错误
#param warning(disable:4996)  
  1. 使用VS自定义的函数
scanf_s("%s", buf);

以上三种方法都可以使编译通过,但都有不足之处,其最大的问题在与跨平台编译,相对来说第二种方法稍好,在Linux平台下编译的话,不用处理源代码便可以

VS中查看内存

在调试的时候可以查看内存在变化情况,这是VS最好用的功能之一
要使用内存查看,在程序调试的时候就需要存在断点
其步骤为:加断点 -> Debug模式 -> 调试 -> 窗口 -> 内存
然后按照内存地址,便可以查看到内存变化情况

dll相关

在一个exe程序中是不可以修改另一个exe程序的,要实现这一操作,就需要dll实现,这也是外挂的基本原理
生成dll的步骤为:项目 -> 属性 -> 常规 -> 配置类型:dll -> 生成解决方案
这样,一个dll就生成成功了,在dll中的函数需要用相关标识导出,这样才能被运用

__declspec(dllexport) void go()
{
    int *p = 0x0011dd;
    *p = 100;
}

指针的一些相关知识

指针存储的是内存地址

int i = 0;
int *p = &i;

无论何种类型的指针,其大小都是一样的
指针之所以要有数据类型,是因为指针取值的时候要知道其读取规则,指针得到一个内存地址,知道了存储的位置,但却不知道需要读取的长度,此时数据类型就指明了指针需要读取的长度
空指针:*p = NULL;,其指向地址为零的位置
多级指针:指针存储的是地址,而指针变量也有地址,也就是说,指针可以存储指针,这就是多级指针
指针的运算:一般只有在数组遍历的时候才有意义,这是由于数组的顺序排列导致的

数组简要说明

arrayName <=> &arrayName <=> &arrayName[0]
数组长度:sizeof(arr) / sizeof(type)
arr[i][j] <=> *(*(a + i) + j)
&arr[i][j] <=> (*(a + i) + j)
数组的[]在编译器底层做了类似于重定义的操作

int arr[5];
int i = 0;
for(; i < 5; i ++)
{
    arr[i] = i;
}

而在[]符号出现以前,数组的操作是基于指针的

int arr[5];
int *p = arr;
int i = 0;
for(; p < arr + 5; p++)
{
    *p = i;
    i++;
}

当在栈中定义数组,数组定义过大时,会造成栈内存溢出
在Windows下,栈内存的大小为2M

函数指针

函数指针在NDK开发中有着大量运用,是重点内容
函数指针实例:

int add(int a, int b)
{
    return a + b;
}

int minus(int a, int b)
{
    return a - b;
}

void test(int(*func_p)(int a, int b), int m, int n)
{
    int x = func_p(m, n);
    printf("%d\n", x);
}

void main()
{
    test(add, 1, 2); //执行加法运算
    test(minus, 2, 1); //执行减法运算
    getchar();
}

生成随机数

在C语言中,生成随机数是很重要的一个运用

srand((unsigned)time(NULL)); //time为随机数种子,如果没有这一步,生成的随机数一直固定
rand(); //此步骤生成随机数

C语言的内存划分

C语言中,对内存进行了抽象的划分,而这些划分在正是内存中是不存在的
栈区(stack),堆区(heap),全局区或静态区,字符常量区,程序代码区

栈内存自动释放,对内存手动释放

有了这些只是以后,要创建大型数组时候,就可以在堆内存中创建了
静态内存分配创建数组,数组的大小在创建的时候便已经固定:

int a[10]; //(此处不考虑C99才有的变长数组)

动态分配内存:

int *p = malloc(len * sizeof(int)); //其含义是创建一块大小为len*sizeof(int)大小的堆内存
//要操作可以使用p[i]
free(p)

在堆内存中开辟的空间,在使用完毕,必须使用free释放,否则会造成内存溢出

if(p != NULL)
{
    free(p);
    p = NULL;
}

一般使用malloc开辟的内存,需要用memset进行初始化,而使用calloc分配的内存已经初始化过了

如果分配的内存不够用,此时就需要使用realloc重新分配内存

int *p2 = realloc(p, sizeof(int) * (len + addlen));

重新分配内存,可能出现如下问题

  • 缩小:缩小的那一部分消失
  • 扩大
    • 若后面有足够的空间,扩展并返回原指针
    • 若后面空间不足,指到新空间,并将原有的值复制过去,清除现有数据并返回新地址
    • 如果申请失败,返回NULL,原来指针仍然有效

字符的修改问题

  • 字符数组存储在字符串中,那么可以被修改
char str[] = {'c','h','i','n','a','\0'};
char str[6] = {'c','h','i','n','a'};
char str[10] = "china";
str[0] = 's'; //可以修改的本质在于字符数组存在于栈内存中
  • 字符指针,不可修改
char *p = "china";
p[0] = 's'; //此时会报错,因为此时的字符存储在字符常量区,不可被修改
  • 字符操作的一些常用函数:strcpy, strcat, strchr, strstr, strcmp等,此处不再一一赘述,详细用法可参考C标准库-string.h以及字符串函数

结构体相关内容

结构体也是一种数据类型

struct Man
{
    char name[64];
    int age;
};

初始化结构体变量

//方法一
struct Man m1 = {"jack", 20};
//方法二
struct Man m2;
strcpy(m2.name, "jack");
m2.age = 20;

当结构体中存在指针的时候,需要使用strcpy进行赋值,否则会造成指针指向内容被释放,产生野指针的情况
结构体的其他写法:

struct Man
{
    char *name;
    int age;
}m1; //结构体变量名
struct Man
{
    char *name;
    int age;
}m1,m2 = {"jack", 20};

匿名结构体:用于控制结构体变量的个数,相当于单例

struct
{
    char *name;
    int age;
}m1;

结构体中允许嵌套结构体
结构体与指针

struct Man m1 = {"jack", 20};
struct Man *p = &m1;
//m1.name m1.age
//(*p).name (*p).age
//p->name p->age

结构体大小(字节对齐)
结构体的动态内存分配

struct Man *p_m = (struct Man *)malloc(sizeof(struct Man) * 10);
struct Man *p = p_m;
p->name = "jack";
p->age = 20;
p++;
p->name = "alen";
p->age = 19;
···
free(p_m);
p_m = NULL;

typedef类型取别名

typedef int jint;
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;

结构体取别名

typedef struct _Man
{
    char name[64];
    int age;
}Man,*ManP; //结构体别名和结构体指针别名,二者不存在必然联系,除非建立关联ManP = &Man

结构体的函数指针成员

struct Girl
{
    char *name;
    int age;
    void (*sayHi)(char *);
}
void sayHi(char* text){}
void main()
{
    struct Girl girl;
    girl.name = "Lucy";
    girl.age = 18;
    girl.sayHi = sayHi;
    girl.sayHi("Hi");
}

联合体

不同类型的变量共同占用一段内存,任何时候只有一个成员
联合体的大小等于最大成员所占的字节数

union my_value{
	int x;
	int y;
	double z;
};

void main(){
	union my_value d1;
	d1.x = 90;
	d1.y = 100; //最后一次赋值有效
	printf("%d,%d,%lf\n", d1.x, d1.y, d1.z);
	system("pause");
}

枚举

列举所有情况
限定值,保证数据的安全性

enum day
{
	Monday,
	Tuesday = 2, //此时,Tuesday为2,下一个在没有指定的情况下为3
	Wednesday,
	Thursday,
	Friday,
	Saturday,
	Sunday
};

void main(){
	enum day d = Monday;
	printf("%d\n", d);
	getchar();
}

可以任意指定位置
枚举的值必须为所列举的值

文本操作

读取文本文件

char *path = "C:\\a.txt"; //路径
FILE *fp = fopen(path, "r");
if(fp == NULL)
{
    printf("文件打开失败");
    return;
}
char buf[64] = { 0 }; //缓冲
while(fgets(buf, 64, fp))
{
    printf("%s", buf);
}
fclose(fp); //关闭

写入文本文件

char *path = "C:\\b.txt";
FILE *fp = fopen(path, "w");
char *text = "test text";
fputs(text, fp);
fclose(fp);

逻辑:路径 -> 打开 -> 读取/写入 -> 关闭

c读写文本文件和二进制文件进体现在回车换行符

  • 写文本时,遇到"\n",转化为"\r\n"
  • 度文本时,遇到"\r\n",转化为"\n"

文件复制

char *read_path = "C:\\test.png";
char *write_path = "C:\\test_copy.png";
FILE *read_fp = fopen(read_path, "rb");
FILE *write_fp = fopen(write_path, "wb");
int buf[64] = 0;
int len = 0;
while(len = fread(buf, sizeof(int), 64, read_fp) != 0)
{
    fwrite(buf, sizeof(int), len, write_fp);
}
fclose(read_fp);
fclose(write_fp);

获取文件大小

char *read_path = "C:\\test.png";
FILE *fp = fopen(read_path, "r");
fseek(fp, 0, SEEK_END); //SEEK_END文件末尾,0偏移量
long filesize = ftell(fp); //返回当前文件指针,相对于文件开头的位置

文本文件加解密

//加密
void crypt(char *normal_path, char *crypt_path)
{
    FILE *normal_fp = fopen(normal_path, "r");
    FILE *crypt_fp = fopen(crypt_path, "w");
    int ch = 0; //一次读取一个字符
    while((ch = fgetc(normal_fp)) != EOF)
    {
        fputc(ch ^ 8, crypt_fp);
    }
    fclose(mormal_fp);
    fclose(crypt_fp);
}
//解密
void decrypt(char *crypt_path, char *decrypt_path)
{
    FILE *crypt_fp = fopen(crypt_path, "r");
    FILE *decrypt_fp = fopen(decrypt_path, "w");
    int ch = 0;
    while((ch = fgetc(crypt_fp)) != EOF)
    {
        fputc(ch ^ 8, decrypt_fp);
    }
    fclose(crypt_fp);
    fclose(decrypt_fp);
}

二进制文件加解密
和文本文件类似,不过是在读取和写入时候采用"rb"与"wb"

void crypt(char *normal_path, char *crypt_path)
{
    FILE *normal_fp = fopen(normal_path, "rb");
    FILE *crypt_fp = fopen(crypt_path, "wb");
    int ch = 0; //一次读取一个字符
    while((ch = fgetc(normal_fp)) != EOF)
    {
        fputc(ch ^ 8, crypt_fp);
    }
    fclose(mormal_fp);
    fclose(crypt_fp);
}

日志输出

__VA_ARGS__可变参数

#define LOG(FORMAT,...) printf(##FORMAT,__VA_ARGS__);

日志区分级别

#define LOG_I(FORMAT,...) printf("INFO:"); printf(##FORMAT,__VA_ARGS__);
#define LOG_I(FORMAT,...) printf("ERRO:"); printf(##FORMAT,__VA_ARGS__);
#define LOG(LEVEL,FORMAT,...) printf("##LEVEL"); printf(##FORMAT,__VA_ARGS__);
#define LOG_I(FORMAT,...) LOG("INFO:",##FORMAT,__VA_ARGS__);
#define LOG_W(FORMAT,...) LOG("WARN:",##FORMAT,__VA_ARGS__);
#define LOG_E(FORMAT,...) LOG("ERRO:",##FORMAT,__VA_ARGS__);

Android下的LOG定义

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"jack",FORMAT,##__VA_ARGS__);
LOGI("%s", "test");
__android_log_print(ANDROID_LOG_INFO,"jack","%S","test");

猜你喜欢

转载自blog.csdn.net/cj5785/article/details/89048638