本文简要回顾了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这样的函数被定义为不安全函数
要使用这些函数,就需要将其编译错误去掉,常用的方法有三种
- 添加宏定义,生效于单个文件
#define _CRT_SECURE_NO_WARNINGS
- 在设置中去除,生效于整个项目
项目->属性->配置属性->C/C++ -> 预处理器 -> 预处理器定义,增加:_CRT_SECURE_NO_DEPRECATE
- 去除编译时的错误
#param warning(disable:4996)
- 使用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");