LCODER之JNI系列:C语言基础

376 阅读4分钟

1. 基础知识

.h .hpp 声明文件
.c .cpp 实现文件
#include 后面跟 "" 表示寻找自己写的资源 后面跟<>表示寻找系统的资源

2. 打印:需要占位

printf("i的值:%d\n",i); // int 类型 
printf("i的值:%lf\n",d); // double类型
printf("i的值:%f\n",f); // float 
printf("i的值:%d\n",l); // long
printf("i的值:%d\n",s); // short
printf("i的值:%c\n",c);// char
printf("i的地址:%p\n",c);// p : 地址占位符`

3. 字符串类型

char * str = "derry";
//打印:
printf("i的值:%s\n",str);// 字符串

4. 常用的api

sizeof() 获取字节数
sizeof(int); // int类型数据所占字节数
sizeof(double); // double类型数据所占字节数
sizeof(char); // char类型数据所占字节数

5.指针(万物皆地址,指针 == 地址)

5.1 地址:
int num = 10000;
printf("number变量的地址:%p\n",&num); // %p 地址占位符  & 取地址符
num指向10000的地址  `
5.2 指针:

& 取地址符; * 取值.

int num_int = 100;
double num_double = 200;
printf("num_int的值:%d\n",num_int);
printf("num_double的值:%lf\n",num_double);
printf("num_int的值:%d\n",*(&num_int));
printf("num_int的值:%lf\n",*(&num_double));
5.3 指针变量: int * | double *
// intP是指针变量,里面存放的是地址:是number_int的地址  * 是取值 取出该地址中的值 
int * intP = &number_int;   
double * doubleP = &number_double;
printf("num_int的值:%d\n",*intP); // 打印的是number_int的值 
printf("num_int的值:%lf\n",*doubleP);
6.函数不能写在main的下面.

如果非要写在main的后面,要在main之前声明一下:

void change(int i);

int main(){
    int i = 100;
    change(i);
    printf("%d,%p\n",i,&i);
    return 0;
}

// 要和之前声明的一致
void change(int i){
    i = 200;
    printf("%d,%p\n",i,&i);
}

打印结果:

200,0032FD8C
100,0032FD98

change函数中的行为参数,和main方法中传递进来的不是同一个参数,调用change方法时,change函数进栈。C/C++编译器会构建一个新的行参,和main函数的i没有半毛钱的关系,内存地址不同,无法修改。

解决: 要改掉main函数的i的值,需要找到main函数中i的地址:

void change(int * i);  // 参数是指针类型的变量
int main(){
    int i = 100;
    change(&i); // 传入的是i的地址
    printf("%d\n",i)
    return 0;
}
void change(int * i){
    *i = 666; // 取出i地址对应的值修改为666
}

输出结果:666

7. 互换数值:
void changeAction(int * a, int * b){
    int temp = *a; // 取出a地址对应的值 赋值给temp
    
    *a = *b ;  // 取出b地址对应的值,赋值给a地址对应的值
    *b = temp; // temp赋值给b地址对应的值
 }

int main(){
    int a = 100;
    int b = 200;
    
    changeAction(&a , &b);
    printf("互换后的效果:%d,%d\n",a,b)
}

8.多级指针:
int num = 999;
int * num_p = &num; // 一级指针 存放num的地址
int ** num_p_p = &num_p; // 二级指针 存放num_p的地址
// 取值: **num_p_p  取出的值是:999
9.数组与指针:

数组的内存地址 = 第一元素的内存地址

int arr[] = {1,2,3,4};
int i = 0;
for(i = 0; i < 4;++i){
    printf("%d\n",arr[i]);
}

printf("arr = %p\n",arr);
printf("&arr = %p\n",&arr);
printf("&arr[0] = %p\n",&arr[0]);
// 打印出来 三者相同

// 数组就是一个内存地址
int * arr_p = arr;
printf("%d\n",*arr_p); // 取出内存地址arr_p的值 : 1

arr_p++; // 指针往数组的下一个移位

指针占用的内存大小: 4个字节 (32位系统) ; 4*2 = 8字节 (64位系统)

10.函数指针:万物皆指针,函数也有自己的指针 。

如何定义:

返回值(*名称)(参数类型,参数类型)
即: void(*method)(int,int)

void add(int num1,int num2){
    printf("num1 + num2 = %d\n",(num1 + num2));
}

// void(*method)(int,int) 就是 函数指针 
void operate(void(*method)(int,int),int num1,int num2)){    method(num1,num2);
}

int main(){
    operate(add,1,2); // 调用add函数
    
    return 0;
}

C语言速查文档:文档中 传入 &t &p 可以直接传入NULL尝试

11.C中的boolean类型

非0即true , 0 == false

12.静态开辟

函数进栈时,定义数组或变量,就是属于静态开辟内存,静态开辟的内存空间大小,不能修改。 栈区的最大值是2M,大于2M会栈溢出。

void staticAction(){
    int arr[5];  // 开辟 4*5 = 20 字节
    
    for(int i = 0;i<5;++i){
        arr[i] = i ;
        printf("%d,%p\n",*(arr+i),arr+i);
    }
}
// 虽然在死循环中开辟内存,但不会导致栈溢出异常,因为每次调用进栈,每次调用完毕就出栈
// 出栈后会释放所有的栈成员
int main(){
    while(9){
        sleep(100);
        staticAction(); // 在死循环中调用staticAction 每次调用都会进栈,静态开辟内存
    }
    return 0;
}
13.动态开辟:

使用malloc等开辟堆区的内存,凡是在堆区开辟内存空间,都属于动态开辟。 堆区的最大值没有确定值,但基本上不用担心溢出。

动态开辟的内存,不会被释放

// C开发的过程中,不能出现悬空指针和野指针,把二者置为NULL即可
//  int * p = NULL;
void dynamicAction(){
    int * p ; // 野指针,没有地址的指针
    int * arr = malloc(1 * 1024 * 1024);
    printf("dynamicAction函数,arr自己的内存地址:%p,堆区开辟的内存地址:%p\n",&arr,arr);
    
    // 重复释放会导致崩溃,所以先判断一下
    if(arr){
     free(arr);
    // 释放之后,需要把arr指向NULL 否则会出现悬空指针 
    // 也就是 arr指向一块被释放掉的内存 
    arr = NULL;  // NULL :0x0000
    }
}

int main(){
    while(9){
        sleep(100);
        dynamicAction();
    }
}

动态开辟的使用场景:

int * arr = malloc(sizeof(int) * 8); // 动态开辟8 * 4 个字节的大小
for(int i = 0;i < 8;i++){
    arr[i] = i;
}
动态开辟 realloc 在堆中追加开辟内存

使用方法:

int * arr = (int *)realloc(arr,sizeof(int)*(num + new_num));
realloc(void * 首次开辟的指针,size总大小);
//1.新开辟的内存的指针大部分情况下和首次开辟的指针一样。
//2.传入参数解释:首次开辟的指针、size总大小,传这两个参数的目的为了避免内存空间不够,
//3.当内存不够时,首次开辟指针是为了copy前面的几个数值。

字符串的两种表现方式:

int main(){
    // '\0'是给printf的,printf遇到\0才会停止打印 否则会在derry后面输出系统值
    char str[] = {'D','e','r','r','y','\0'};
    str[2] = 'z';
    printf("第一种方式:&s\n",str);
    
    // 运行这段代码时,会造成崩溃,因为str2是一个地址值,
    // 指向的是全局区里面的数据,全局区里面的数据拒绝访问
    char * str2 = "Derry";
    str2[2] = 'z';
    printf("第二种方式:%s\n",str);
}

指针挪动获取字符串信息:

int getLen(char * string){
    int count = 0;
    while(*string){ // *string != '\0' 我就一直循环
        string++;// 指针挪动  string 是数组,数组string指向的是数组第一个数值所在的地址
        count++; 
    }
    return count;
}

// 这种方式无法获得真正的len
void getLen2(int intarr[]){
    int len = sizeof(intarr)/sizeof(int);
    printf("getLen的长度是%d\n",len); // 这里打印1 
}

void getLen3(int * resultLen,int intarr[]){
    // 手动计算长度
    int count = 0;
    while(*intarr){
        intarr++;
        count++;
    }
    *resultLen = count; //找到resultLen地址 , 把count值放入resultLen中 
}



int main(){
    char string[] = {'A','B','C','D','0','\0'}
    int r = getLen(string);
    printf("长度是:%d\n",r);// 输出结果是5
    
    int intarr[] = {1,2,3,4,5,6,7};
    int len = sizeof(intarr)/sizeof(int);
    print("len长度是:%d\n",len);  // 打印7 
    
    // c/c++ 编译器,数组作为参数传递,会把数组优化成指针(为了高效率)
    getLen2(intarr);
    
    int result;
    getLen3(&result,intarr); // &result 取出result地址 给函数,函数再给地址赋值
    printf("getLen的长度是:%d\n",result);
    
    return 0;
}

CLion快捷键: 两次shift 输入File Encoding 改变Encoding

14.字符串的操作
14.1 字符串的转换
14.1.1 atoi 转换成整型
int main(){
    char * num = "1"; // 输出1
    num = "12.68xx";  // 输出12
    
    int result = atoi(num);
    if(result){ // 非0即true 不是0进入if  0就是转换失败了
        printf("恭喜你转换成功:%d\n",result)
    }
}
atof 转换成double类型 (使用方法同上)
14.2 字符串的比较

方法 :使用函数strcmp(区分大小写) ,返回0表示相等,返回1不相等 strcmpi(不区分大小写)

14.3 字符串的查找、包含、拼接

方法:使用函数strstr,在串中查找指定字符串的第一次出现的位置,返回指针。是否包含,也使用这个函数。

char * pop = strstr(str1,str2); 
if(pop){ // 非NULL 进入 查找到了
    printf("查找到了,pop的值是:%s\n",pop);
}else{
    printf("没有查找到\n");
}
14.4 字符串的拼接:

方法:使用函数strcat

char destination[25]; // 容器 大小25
char * blank = "--到--" , *CPP = "C++" , *Java = "Java";
strcpy(destination,CPP); // 先copy到数组里面去
strcat(destination,blank);// 然后 再拼接
strcat(destination,Java);// 然后再拼接
printf("拼接后的结果:%s\n",destination);
14.5 大小写转换
void lower(char * dest,char * name){
    char * temp;// 避免破坏name指针
    while(temp){
        *dest = tolower(*name);
        temp++;// 挪动指针的位置++ 
        dest++; // 挪动指针的位置 ++ 挪动一个存储一个 挪动一个存储一个
    }
    *dest = '\0';  // 指针最后一个赋值'\0' 避免printf打印系统值
}

int main(){
    char * name = "Derry";
    
    // 先定义结果
    char dest[20];
    lower(dest,name);
    printf("小写转换后的结构是:%s\n",dest);
    return 0;
}
14.6 字符串截取
void subStr1(char * result,char * str,int start,int end){
    char * temp = str;// 定义临时指针,不破坏str指针
    int count = 0; // 记录当前的位置
    while(*temp){
        if(count > start && count < end){
            *result = *temp; 
             result++; // 接收值也要挪动,挪动指针来接收 temp给我的值
        }
        temp ++;
       count ++; 
    }
}

// 二级指针,使用的时候传递地址进来 使用&取地址 
void subStr2(char ** result,char * str,int start,int end){
    char * temp = str; //定义临时指针,不破坏str
    
    // char resultArr[end - start];// 栈内存,在方法结束时,会被释放
    // 开辟堆内存 
    char * resultArr = malloc(end - start);
    int count = 0;
    for(int i = start;i<end;++i){
        resultArr[count] = *(temp+i);
        count++;
    }
    // 取出二级指针中存放的一级指针
    *result = resultArr;
    printf("%s\n",resultArr);
    
    // 这种方式,方法调用完之后要回收堆空间
}

void subStr3(char * result,char * str,int start,int end){
    for(int i = start; i < end ;++i){
        *(result +i) = *(str+i);
    }
}

void subStr4(char * result,char * str,int start,int end){
    // 参数1 : 最终copy到result容器里面
    // 参数2:  直接指针挪动到r
    // 参数3:  从r开始挪动,挪动多少
    strncpy(result,str+start,end-start);
}