Android开发Android开发经验谈Android技术知识

NDK开发:C语言基础

2019-09-18  本文已影响0人  小村医

一、指针

指针和变量的区别:指针是有类型的,地址没有类型
指针的地址告诉你从什么位置开始读取数据,类型告诉你读取到什么位置结束

1、多级指针
指针保存的是一个变量地址,这个变量也可以是一个指针变量

int main()
{
    int i = 10;
    //p1上保存的是i的地址
    int *p1 = &i;
    //p2上保存的是p1的地址
    int **p2 = &p1;
    printf("i的地址:%#x,i的值:%d\n", &i, i);
    printf("p1的地址:%#x,i的值:%#x\n", &p1, p1);
    printf("p2的地址:%#x,i的值:%#x\n", &p2, p2);
    return 0;
}

输出结果:

i的地址:0xe11fa568,i的值:10
p1的地址:0xe11fa560,i的值:0xe11fa568
p2的地址:0xe11fa558,i的值:0xe11fa560

使用二级指针修改变量的值

*p2 = 90;

2、指针运算

int main()
{
    int ids[] = {10, 39, 48, 33, 20};
    printf("%#x\n", ids);
    printf("%#x\n", &ids[0]);
    return 0;
}

输出结果:

0xe2ed7550
0xe2ed7550

从上面的运行结果可以发现:数组的变量名(ids)就是数组的首地址

int main()
{
    int ids[] = {10, 39, 48, 33, 20};
    printf("%#x\n", ids);
    printf("%#x\n", &ids);
    printf("%#x\n", &ids[0]);
    //指针变量
    int *p = ids;
    printf("%d\n", *p);
    p++;
    printf("%d\n", *p);
    return 0;
}

输出结果:

0xeb298550
0xeb298550
0xeb298550
10
39

数组在内存中是连续的存储:
指针++ 向后移动sizeof(int)个字节
指针-- 向前移动sizeof(int)个字节

指针的运算一般再数组遍历时才有意义,基于数组在内存中是线性排列的
通过指针给数组赋值:

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

3、函数指针

void log(char *msg)
{
    printf(msg);
}
int main()
{
    // 函数指针
    void (*fun_p)(char *msg) = log;
    fun_p("hello");
    return 0;
}

函数指针主要包括三部分:函数返回值类型、函数指针的名称、函数参数类表

int add(int a, int b)
{
    return a + b;
}
int minus(int a, int b)
{
    return a - b;
}
void msg(int (*fun_p)(int a, int b), int m, int n)
{
    printf("%d\n", fun_p(m, n));
}
int main()
{
    msg(add, 1, 2);
    return 0;
}

输出结果:

3

类似于java中的回调函数,java中传递的是对象,c中传递的是方法指针。

二、动态内存分配

C语言内存分配主要包括:

  1. 栈区(stack):超出限制提示Stack Overflow,栈内存自动分配、自动释放
  2. 堆区(heap):手动分配和自动分配,系统80%的内存都可以分配给应用程序
  3. 全局区或静态区
  4. 字符常量区
  5. 程序代码区
//栈内存分配
void stackFun()
{
    int a[1024 * 1024 * 10];
    //  栈内存自动释放
}
// 堆内存
void heapFun()
{
    //分配
    //malloc返回的是void*,可以是任何类型的指针
    int *p = malloc(1024 * 1024 * sizeof(int));
    //释放
    free(p);
}

创建数组,动态指定数组的大小

int main()
{
    int length;
    printf("输入数组长度\n");
    scanf("%d", &length);
    //分配内存
    int *p = malloc(length * sizeof(int));
    //给数组元素赋值
    int i = 0;
    for (; i < length; i++)
    {
        p[i] = rand() % 100;
        printf("%d, %#x\n", p[i], &p[i]);
    }
    //手动释放内存
    free(p);
}

静态内存分配:分配的内存大小是固定的,很容易超出栈内存的最大值,
动态内存分配:在程序运行过程中,动态指定需要使用的内存大小,手动释放后这些内存还可以被重复使用。

内存不够时扩大需要的内存:

int main()
{
    int length = 10;
    //分配内存
    int *p = malloc(length * sizeof(int));
    //给数组元素赋值
    int i = 0;
    for (; i < length; i++)
    {
        p[i] = rand() % 100;
        printf("%d, %#x\n", p[i], &p[i]);
    }
    //增加数组长度
    int addLength = 5;
    //扩大刚刚分配的内存空间
    // realloc参数:1. 原来的内存指针,2. 内存扩大之后的总大小
    //返回的指针可能是原来的指针也可能是新的指针(如果有足有的连续空间则是原来的指针)
    int *p2 = realloc(*p, (addLength + length) * sizeof(int));

    //手动释放内存
    free(*p2);
    free(p);
}

三、字符串

1、使用字符串数组存储字符串

int main()
{
    //字符数组赋值只能在声明时
    char str[] = {'c', 'h', 'i', 'n', 'a', '\0'};
    // char str[] = "china";
    // str ="cna";不能修改
    //可以修改单个字符
    str[0] = 's';
    printf("%s\n", str);
    return 0;
}

2、使用字符指针

    //字符指针可以多次赋值不同字符串
    char *str = "china";
    // *str = 'H'; 不能修改字符串内容
    printf("%s\n", str);

3、常用字符串函数

    char string[] = "This is a string";
    char *ptr, c = 'r';
    ptr = strchr(string, c);

    if (ptr)
    {
        int position = ptr - string;
        printf("The character %c is at position: %d\n", c, position);
    }
    else
    {
        printf("The character was not foundn");
    }

四、结构体

结构体是一个构造类型
把不同的数据类型整合起来成为一个自定义的数据类型
定义结构体

struct Person
{
    /* 成员 */
    char* name;
    int age;
};

使用结构体

int main()
{
    //初始化结构体变量
    //1.
    struct Person p1 = {"Jack", 21};
    //2.
    struct Person p2;
    p2.name = "Jack";
    p2.age = 21;

    //使用
    printf("name:%s,age:%d", p2.name, p2.age);

    return 0;
}

结构体的几种写法

  1. 定义结构体,同时定义变量
struct Person
{
    char *name;
    int age;
} person, p2={"Jack", 21};//person是变量名

int main()
{
    printf("name:%s,age:%d", person.name, person.age);
    return 0;
}
  1. 匿名结构体(控制结构体变量的个数)
struct 
{
    char *name;
    int age;
} person;

int main()
{
    printf("name:%s,age:%d", person.name, person.age);
    return 0;
}
  1. 结构体嵌套
struct  Teacher
{
    char *name;
    int age;
} ;

struct  Studet
{
    char *name;
    int age;
    struct Teacher teacher;
} ;

或者

struct Studet
{
    char *name;
    int age;
    struct TeacherTeacher
    {
        char *name;
        int age;
    } teacher;
};

结构体指针

struct Person p1 = {"Jack", 21};
//结构体指针
struct Person *p = &p1;
printf("name:%s,age:%d", (*p).name, (*p).age);
// ->是(*p).的简写
printf("name:%s,age:%d\n", p->name, p->age);

指针与结构体数组

int main()
{
    struct Person persons[] = {{"Jack", 21}, {"Rose", 20}};
    //遍历结构体数组
    struct Person *p = persons;
    for (; p < persons + sizeof(persons) / sizeof(struct Person); p++)
    {
        printf("name:%s,age:%d\n", p->name, p->age);
    }
    return 0;
}

结构体的大小(字节对齐)

struct Person
{
    int age;
    double weight;
};

int main()
{
    printf("size:%d\n", sizeof(struct Person));
    return 0;
}

输出结果:

size:16

Person中有一个int和一个double类型的成员,按正常理解来说大小应该是4+8=12,但是实际大小是16,这是因为:
结构体的大小是最大基本数据类型的整数倍

结构体与动态内存分配

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

int main()
{
    struct Person *p = malloc(sizeof(struct Person) * 10);
    p->name = "Jack";
    p->age = 21;
    p++;
    p->name = "Rose";
    p->age = 20;
    free(p);
    return 0;
}

五、typedef取别名

typedef int Age;
typedef int* IntP;
int main()
{
    Age a = 30;
    int i = 5;
    IntP p = &i;
    return 0;
}
  1. 不同的名称代表做不同的事情
  2. 不同情况下使用不同的别名
  3. 书写简洁

结构体取别名

typedef struct Person
{
    int age;
    double weight;
} Person , *Per;//Per是结构体指针的别名
//同
typedef struct Person Person;
typedef struct Person* Per;
int main()
{
    Person p = {10, 100};
    Per per = &p;

    return 0;
}

结构体函数指针成员

struct Person
{
    int age;
    char* name;
    void (*log)(char *msg);
};
void log(char* msg){
    printf(msg);
}
int main()
{
    struct Person person;
    person.name="Jack";
    person.age=21;
    person.log =log;
    person.log("hello");
    
    return 0;
}

Person结构体类似于java中的类,age和name类似于属性,log类似于方法

六、联合体(公用体)

不同类型的变量共同占用一块内存,联合体任何时候只有一个成员存在,节省内存 。
联合体内存大小=最大成员所占的内存大小

union Data {
    int x;
    int y;
    double z;
};
int main()
{
    union Data data;
    data.x = 10;
    data.y = 20;
    data.z = 30.0;
    printf("%d,%d,%lf", data.x, data.y, data.z);
    return 0;
}

输出结果:

0,0,30.000000

最后一次赋值有效

七、枚举

enum Week
{
    Sunday,
    Monday,
    Tuesday,
    Thursday,
    Friday,
    Saturday
};
int main()
{
    enum Week day = Monday;
    printf("%#x,%d",&day,day);
    return 0;
}

输出:

0xe178f568,1

八、IO操作

  1. 打开文件 fopen( )
    fopen( )函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:
FILE *fopen( const char * filename, const char * mode );
  1. 关闭文件 fclose( )
    为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:
 int fclose( FILE *fp );

如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。

  1. 写入文件
    下面是把字符写入到流中的最简单的函数:
int fputc( int c, FILE *fp );

函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。
您可以使用下面的函数来把一个字符串写入到流中:

int fputs( const char *s, FILE *fp );

函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF

  1. 读取文件
    下面是从文件读取单个字符的最简单的函数:
int fgetc( FILE * fp );

fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF。
下面的函数允许您从流中读取一个字符串:

char *fgets( char *buf, int n, FILE *fp );

函数 fgets() 从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。

如果这个函数在读取最后一个字符之前就遇到一个换行符 '\n' 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。

int main()
{
    char *path = "*********";
    //打开文件
    FILE *file = fopen(path, "r");
    if (file == NULL)
    {
        printf("打开失败");
        return 0;
    }
    //读取文件
    char buffer[100];
    while (fgets(buffer, 100, file))
    {
        printf("%s",buffer);
    }
    //关闭
    fclose(file);

    return 0;
}

C读写二进制文件和文本文件的区别提现再回车换行符:
写文本时,遇到\n会转换成\r\n;
读文本是,遇到\r\n会转换成\n

        char *read_path = "****";
    char *write_path = "****";
    //读文件 b字符表示操作二进制文件
    FILE *read_fp = fopen(read_path, "rb");
    //写文件
    FILE *write_fp = fopen(write_path, "wb");
    
    //复制
        char buff[50]; //缓冲区
    int len = 0; //每次读取到的数据长度
    while ((len = fread(buff, sizeof(char), 50, read_fp)) != 0){
        //将读取到的内容写入新文件
        fwrite(buff,sizeof(int),len,write_fp);
    }
    //关闭
    fclose(read_fp);
    fclose(write_fp);

获取文件的大小

void main(){
    char *read_path = "****";
    FILE *fp = fopen(read_path, "r");
    //重新定位文件指针
    //SEEK_END:文件末尾,0偏移量
    fseek(fp,0,SEEK_END);
    // 返回文件当前指针相对于文件开头的位偏移量
    long filesize = ftell(fp);
    printf("%d\n",filesize);

}

九、预处理

c语言执行的流程:

  1. 编译:形成目标代码(.obj)
  2. 链接:将目标代码与c函数库连接合并,形成最终的可执行文件
  3. 执行

预编译(预处理),为编译做准备,完成代码文本的替换工作。

头文件告诉编译器有这样一个函数,连接器负责找到这个函数的实现。

define

  1. 定义标识
 ifdef __cplusplus,标识支持C++语法

防止文件重复引入:

#ifndef xx
#define xx
#include "x.h"
#endif

或者
//该头文件只被包含一次,让编译器自动处理好循环包含问题
#pragma once
#include "x.h"
  1. 定义常数
#define MAX 100
void main(){
    int i = 90;
    if (i < MAX) {
        printf("比MAX小");
    }
}
  1. 定义“宏函数”
    webrtc中 JNI函数明后名称很长,也是通过JOW宏函数缩短函数名
void  com_jni_read(){
    printf("read");
}
void  com_jni_write(){
    printf("write");
}

# define jni(NAME) com_jni_##NAME();

void main(){
    jni(write);//替换成com_jni_write
}

//日志输出
//__VA_ARGS__ 可变参数
#define LOG(FORMAT,...) printf(FORMAT, __VA_ARGS__)
上一篇下一篇

猜你喜欢

热点阅读