• NDK学习笔记-C语言


    本文简要回顾了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
    ", 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',''};
    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
    ", 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
    ", 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读写文本文件和二进制文件进体现在回车换行符

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

    文件复制

    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");
    
  • 相关阅读:
    lombok的介绍及使用
    java后端导入excel将数据写入数据库
    java后端导出excel表格
    eclipse maven打war包
    java后端树形菜单使用递归方法
    mybatis一对多查询
    @transactional作用和事务
    zookeeper安装
    Solr单机版安装
    jstat 简介(2)
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664685.html
Copyright © 2020-2023  润新知