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语言内存分配主要包括:
- 栈区(stack):超出限制提示Stack Overflow,栈内存自动分配、自动释放
- 堆区(heap):手动分配和自动分配,系统80%的内存都可以分配给应用程序
- 全局区或静态区
- 字符常量区
- 程序代码区
//栈内存分配
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;
}
结构体的几种写法
- 定义结构体,同时定义变量
struct Person
{
char *name;
int age;
} person, p2={"Jack", 21};//person是变量名
int main()
{
printf("name:%s,age:%d", person.name, person.age);
return 0;
}
- 匿名结构体(控制结构体变量的个数)
struct
{
char *name;
int age;
} person;
int main()
{
printf("name:%s,age:%d", person.name, person.age);
return 0;
}
- 结构体嵌套
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;
}
- 不同的名称代表做不同的事情
- 不同情况下使用不同的别名
- 书写简洁
结构体取别名
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操作
-
打开文件 fopen( )
fopen( )函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:
FILE *fopen( const char * filename, const char * mode );
-
关闭文件 fclose( )
为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
-
写入文件
下面是把字符写入到流中的最简单的函数:
int fputc( int c, FILE *fp );
函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。
您可以使用下面的函数来把一个字符串写入到流中:
int fputs( const char *s, FILE *fp );
函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF
-
读取文件
下面是从文件读取单个字符的最简单的函数:
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语言执行的流程:
- 编译:形成目标代码(.obj)
- 链接:将目标代码与c函数库连接合并,形成最终的可执行文件
- 执行
预编译(预处理),为编译做准备,完成代码文本的替换工作。
头文件告诉编译器有这样一个函数,连接器负责找到这个函数的实现。
define
- 定义标识
ifdef __cplusplus,标识支持C++语法
防止文件重复引入:
#ifndef xx
#define xx
#include "x.h"
#endif
或者
//该头文件只被包含一次,让编译器自动处理好循环包含问题
#pragma once
#include "x.h"
- 定义常数
#define MAX 100
void main(){
int i = 90;
if (i < MAX) {
printf("比MAX小");
}
}
- 定义“宏函数”
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__)