NDK开发:C语言基础

一、指针

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

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、常用字符串函数

  • stpcpy
    功 能: 拷贝一个字符串到另一个
    用 法: char *stpcpy(char *destin, char *source);
  • strcat
    功 能: 字符串拼接函数
    用 法: char *strcat(char *destin, char *source);
  • strchr
    功 能: 在一个串中查找给定字符的第一个匹配之处
    用 法: char *strchr(char *str, char c);
    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");
    }
  • strcmp
    功 能: 串比较
    用 法: int strcmp(char *str1, char *str2);
    比较Asic码,str1>str2,返回值 > 0;两串相等,返回0
  • strrchr
    功 能: 在串中查找指定字符的最后一个出现
    用 法: char *strrchr(char *str, char c);
  • strrev
    功 能: 串倒转
    用 法: char *strrev(char *str);

四、结构体

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

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__)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,419评论 3 44
  • 版权声明:本文为 gfson 原创文章,转载请注明出处。注:作者水平有限,文中如有不恰当之处,请予以指正,万分感谢...
    gfson阅读 2,878评论 0 6
  • 一、框架 1、Mac系统及常用工具、进制;C数据类型、常量变量、运算符、表达式、格式化输入输出 2、关系运算符、逻...
    师景福阅读 667评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,068评论 1 32
  • 基于基本类型和浮点类型创建(C语言:派生类型) 1.数组(简介) 1.数组声明 ·存储在每个元素中的值的类型 ·数...
    MelloCat阅读 653评论 0 3