C语言须知和开始
初识C语言
1987年布莱恩柯林汉和丹尼斯里奇合作的C语言程序设计第一版是公认的C标准:
- 第一个ANSI/ISO C标准
通常叫做C89(因为ANSI与1989年批准该标准)或C90(因为ISO与1990奶奶批准该标准),由于ANSI先公布C标准,因此业界人士通常使用ANSI C。
该委员会指定的指导原则中,最有趣的可能是:保持C的精神。委员会在表述这一精神时列出了以下几点:- 信任程序员
- 不要妨碍程序员做需要做的事
- 保持语言精练简单
- 只提供一种方法执行一项操作
- 让程序运行更快,即使不能保证其可移植性
- C99标准 1994年,ANSI/ISO联合委员会开始修订C标准,最终发布了C99标准。
- 支持国际化编程 例如:提供多种方法处理国际字符集
- 调整现有实现致力于解决明显缺陷 例如:需要将C移至64位处理器
- 为适应和工程项目中的关键数值计算,提高C的适应性。
- C11标准 标准委员会在2007年承诺C的标准下一个版本是C1X,2011年终于发布了C11标准。此次,委员会提出了一些新的指导原则。处于对当前编程安全的担忧,不那么强调“信任程序员”目标了。而且,供应商并未像对C90nayang很好地接受和支持C99。这使得C99的一些特性称为C11的可选项。因为委员会认为,不应要求服务小型机市场的供应商支持其目标环境中用不到的特性。另外需要强调的是,修订标准的原因不是因为原标准不能用,而是需要跟进新的技术,比如,新标准添加了可选项支持当前使用多处理器的计算机。对于C11标准,我们浅尝辄止。
开发工具
在Windows软件开发中,Microsoft Visual Studio及其免费版本Microsoft Visual Studio Express 都久负盛名,它们与C标准的关系也很重要。然而,微软鼓励程序员从C转向C++和 C#。虽然Visual Studio 支持C89/C90,但是到目前为止,它只选择性的支持那些在C++新特性中能找到的C标准(如,long long 类型)。而且,自2012版本起,Visual Studio 不再把C作为项目类型的选项。尽管如此,本书中的绝大多数程序仍可用Visual Studio来编译。
- Visual Sutdio 32位以下面的方式
在新建项目时,选择C++选项,然后选择【Win32 控制台应用程序】,在应用设置中选择【空项目】。几乎所有的C程序都能与C++程序兼容。所以,本书中的绝大多数C程序都可作为C++程序运行。或者,在选择C++选项后,将默认的源文件扩展名.cpp替换为.C,编译器便会使用C语言的规则代替C++。 - Visual Studio 64位 以下面的方式:
- 在新建项目时,选择C++选项,然后选择Windows桌面向导,点击确认-项目新建成功,其他选项里面选择空项目-点击确认,在
源文件
新建一个项,选择C++文件,把.cpp更改为.c。 然后就可以运行了
这里需要注意的是 一个拥有main()方法的.c文件就是一个独立的项目,所以需要运行更多的main()方法,那么只有一种办法就是点击解决方案第一个右键继续新建一个项目(循环上一步),然后编码,编码完成后,点击该项目右键,设置为启动项目。 - 在新建项目时,选择C++选项,然后选择Windows控制台应用程序,点击确认-项目新建成功,在
源文件
里面有个 xxx(项目名).cpp 更改为.c,报错的代码删除,就可以直接运行了。
C 语言语法
基本要求和变更
int main(void) void main() void main(String[] args) 也可以像java一样这样.
/* 双斜线 单行注释 是C99标准以后加的
C90,要求吧变量声明在块的顶部,其他语句不能任何声明的前面。 C99和C11 改变了这个的限制,可以随意声明。
C99 和C11 允许使用更长的标识符名(变量名),但是编译器只识别前63个字符。C90只允许6个字符,旧式编译器通常最多只允许8个字符。
变量名可以用 大、小写字母和下划线来命名。第一个字符必须是字符或下划线,不能是数字。
*/
复制代码
数据类型和使用
基本数据类型和使用
- 平常基本使用就使用 int long short unsigned char float double 就完了, 所以只需要有哪些东西存在就行了,真的在使用比较大的数做运算处理的时候, 再去使用它们.
数据类型 | printf |
---|---|
int | %d |
short | %d 或者 %hd |
long | %d |
float | %d 或者 %hd |
double | %d |
char | %d 或者 %hd |
16进制 | %x |
8进制 | %o |
字符串 | %s |
16进制long 类型 | %lx |
8进制打印long 类型 | %lo |
字符串 | %s |
指数基数法的浮点数 | %e (如果系统支持16进制格式的浮点数),可用a和A分别代替e和E |
short int | %d |
long int | %ld (在32位环境中其实是一样的long int 和long) |
long long | %lld |
long long int | %lld |
unsigned long | %lu |
int32_t(是int类型的别名,是精确宽度整数类型,int32_t表示的宽度正好为是32位的时候,但是计算机底层系统不支持。因此这是个可选项。如果系统不支持精确宽度整数类型怎么办?C99和C11提供了第2类别名集合:) | %" PRId32 " |
int_least8_t (至少是8位有符号的整数值的一个别名) | %" PRId32 " |
int_fast8_t (至少8位有符号值而言运算最快的整数类型别名) | %" PRId32 " |
intmax_t (C99定义了 intmax_t (最大的有符号整数) 类似的还有 unitmax_t 表示最大无符号整数类型。 这些类型可能比 long long 和 unsigned long 类型更大。) | %" PRId32 " |
int32_t | %" PRId32 "(上面的这些可移植类型,在printf 打印的时候 有些使用 %d,有些使用 %ld ,C标准针对这一情况提供了 RPId32 \ 64 字符串宏来显示可移植类型。 还有 PR (Id li lo lu ) 很多种 宏) |
//stdudio.h 相当于java 的lang包,在C中studio.h 是C语言的一个标准库, .h代表只有函数的声明,没有函数的实现,因为C语言是系统底层的语言,所以他的这些函数,都在window,linux 有不同的实现,在编译器把.c 或者C++的.cpp文件编译完成后,执行的时候我们调用的这些声明函数就会去系统底层找自己的实现.
#includeo //include 预处理指令,类似java 的import
void main(){
printf("n = %d, n squared = %ld, n cubed = %f\n", int, long,float);
}
/*
char 类型:
char 类型存储的一个字符,但是从技术层面看,char是整数类型。因为char类型实际上存储的是整数而不是字符,计算机使用数字编码来处理字符,即用特定整数表示特定的字符,参考ASCII编码表。
转义序列:
// \a 警报
// \b 退格
// \f 换页
// \n 换行
// \r 回车
// \t 水平制表符
// \v 垂直制表符
// \\ 反斜杠(\)
// \' 单引号
// \" 双引号
// \? 问号
// \0oo 八进制(oo必须是有效的八进制数,即每个o可表示0~7中的一个数)
// \xhh 十六进制(hh必须是有效的十六进制数,即每个h可表示0~f中的一个数)
*/
复制代码
指数记数法(插曲)
因为上面有个指数记数,学习了就记个笔记.
数字 | 科学计数法 | 指数记数法 |
---|---|---|
100000000 | 1 X 10(9次方) | 1.0e9 |
322.56 | 3.2256 X 10(2次方) | 3.2256e2 |
0.000056 | 5.6 X 10(-5次方) | 5.6e-5 |
C标准规定,float类型必须至少能表示 10(-37次方) ~ 10 (37次方), float 至少精确到小数点后6位。
// double类型和float类型的最小取值范围相同,但是至少必须能表示10位有效数字。 (经验: 不管系统差异给小数部分分多少,一般情况下,double至少有13位小数精度)
// C99 标准添加了一种新的浮点型常量格式--用十六进制表示浮点型常量。十六进制前加(0x或0X),用p 或P 分别代表e或E, 例如: 0xa.1fp10 ,打印16进制浮点型 使用 %e或%le
// 使用C语言,很多公司都有系统化的命名规范,比如 int i_smart; unsigned short us_versmart,一看命名就知道是什么类型
// 复数和虚数:
复制代码
定义常量
#define DENSITY 62.4 // 定义一个常量
#include
#include
void main()
{
char name[40];
/*
定义常量:
#define DENSITY 62.4 这样像include 一样定义的常量,程序编译时,程序中的所有DENSITY 都被替换成了62.4,这个过程称为编译时替换,通常这种常量也叫做明示常量
C90 标准新增 const 关键字, const int MONTHS = 12; 用于限定一个变量为只读.
C头文件 limits.h 和 float.h ,
C 语言 char类型数组必须以\0(ASCII对应0) 结束,那么40个就只能存储39个字符. 'x' 和"x" 就不同 一个是char基本类型,一个是char数组类型(后面就是\0)
sizeof(name) = 40; strlen(name) = 实际存储的字符长度;
*/
}
复制代码
指针
- 指针是什么?
指针是一个变量,来存储int char ..... 用来存储各种类型定义的变量的内存地址. 而且指针还需要有类型. - 指针还需要有类型?
那么在C中指针为什么要有类型,因为指针存储的是一个变量的内存地址,那么如果有人要使用指针去拿取内存地址的数据的时候,该怎么去拿呢,即使拿出来又该怎么判定它是什么类型的呢.
指针有类型,地址没有类型.,地址只是开始的位置,类型读取到上面位置结束,(例如:如果你是int类型 加入int占4字节,那么指针的类型就告诉你读到哪里结束. 内存地址告诉你从内存哪个位置开始读.) - 使用 & 去地址符 (int i = 89; int *p = &i); 定义一个变量 i = 89, &i 拿到 i 变量的内存地址,赋值给 int *p 一个int 类型的指针变量.
int main(void)
{
int i = 89;
int *p = &i; //
double j = 78.9;
//赋值给double 类型变量的地址
p = &j;
printf("double size %d\n",sizeof(double));
printf("%#x,%lf\n",p,*p);
}
复制代码
- 空指针 空指针的默认地址为0,访问内存地址0,操作系统不允许.
void main() {
int i = 9;
int *p = NULL;
//p = &i;
printf("%d\n", *p); //空指针的默认地址为0,访问内存地址0,操作系统不允许.
getchar();
}
复制代码
- 二级指针 和多级指针 JNIEnv 是 jni.h 中 _JNIEnv 就是一个JNINativeXXX* 就是一个指针, 多级指针(二级指针),指针保存的是变量的地址,保存的变量还可以是一个指针变量 动态内存分配 二维数组,都需要用的到多级指针
void main() {
int a = 50;
int* p1 = &a;
int** p2 = &p1;
int*** p3 = &p2;
printf("p1 : %#x, p2 : %#x", p1, p2);
int* qP1 = *p3;
int* qP2 = *p2;
int qa = *qP2;
//等同于
int qa1 = ***p3;
printf("qa %d",qa);
//printf("p2 %#x",);
getchar();
}
复制代码
- 指针的运算
void main() {
//int ids[] = {78, 90, 23, 65, 19};
数组变量名ids就是数组的首地址,下面这三个打印出来的地址是一样的
//printf("%#x \n", ids);
//printf("%#x \n", &ids);
//printf("%#x \n", &ids[0]);
指针变量
//int *p = ids;
//printf("%d \n", *p);
指针的加法, +1 向前移动sizeof(数据类型)个字节, 相当于直接移动到数组的下一个index 的地址.
///*数组在内存中是连续存储的,
//*/
//p++;
//printf("%d \n", *p);
//高级写法
int uids[5];
//int i = 0;
//for (int i = 0; i < 5; i++)
//{
// uids[i] = i;
//}
//早些版本的写法,p
int* p = uids;
printf("%#x\n",p);
int i= 0;
for (;p< uids+5; p++)
{
*p = i;
i++;
}
getchar();
}
复制代码
- 函数指针
void msg(char* msg, char* title) {
MessageBox(0, msg,title,0);
}
//函数指针
void main() {
//msg();
printf("%#x msg\n",msg);
printf("%#x msg\n", &msg);
//函数指针 void函数返回值类型 (*fun_p)函数指针的名称,() 函数的参数列表
void(*fun_p)() = msg;
fun_p("xiaoxi内容","biaoti");
getchar();
}
复制代码
- 练习 (用随机数生成一个数组,写一个函数查找最小值,并返回最小数的地址,在主函数总打印出来)
int* getMinPointer(int ids[], int len) {
int i = 0;
int* p = &ids[0];
for (; i< len; i++)
{
if (ids[i] < *p) {
p = &ids[i];
}
}
return p;
}
void main() {
int ids[10];
int i = 0;
//初始化随机数发生器,设置种子,种子不一样,随机数才不一样
//当前时间作为种子
srand((unsigned)time(NULL));
for (int i = 0; i < 10; i++)
{
ids[i] = rand() % 100;//生成随机数 rand不能生成 必须指定种子 模100就是100以内的数.
printf("%d ",ids[i]);
}
int* p_1 = getMinPointer(ids, sizeof(ids) / sizeof(int));
printf("%#x, %d \n",p_1,*p_1);
getchar();
}
复制代码
动态内存
为什么要用动态内存:
静态内存分配,分配内存大小是固定的,问题: 1. 很容易超出栈内存最大值 2.为了防止内存不够用会开辟更多的内存 动态内存分配,在程序运行过程中,动态指定需要使用的内存大小,手动释放,释放之后这些内存还可以供其他程序使用或重新使用.
- 动态内存分配
void main() {
//40M
//stack overflow 错误,栈溢出
//int a[1024 * 1024 * 10]; //在栈区直接分配40M内存,正常来说window下面,一个应用程序分配的栈内存大小在2M,所以这样申请会报stack overflow
/*C语言的内存分配
1.栈区(stack) 自动分配,自动释放 ,一般是局部变量,用完立马就释放了.
2.堆区(heap) 程序员手动分配和释放,可以分配大概操作系统80%左右的内存.
3.全局区或静态区
4.字符常量区
5.程序代码区
*/
//在堆内存上分配一个400M的内存
// malloc 返回的是void* 代表,它可以返回任意类型的指针 , 所以这里
int* p = malloc(1024 * 1024 * 100 * sizeof(int));
getchar();
//释放该内存
free(p);
}
复制代码
- 动态内存分配 +扩充
void main() {
//静态内存分配,数组的大小是固定的
int len;
printf("第一次输入数组的长度:");
scanf("%d", &len);
int* p = malloc(len * sizeof(int));
//int* p = calloc(len, sizeof(int)); //还有一种写法 calloc 不用自己算,传进去它自动帮你算
//赋值
int i = 0;
//p 是内存地址第一个int 4字节的地址, 所以i++ 就直接给内存地址赋值就可以了
for (; i < len; i++)
{
p[i] = rand() % 100;
printf("%d, %#x \n",p[i],&p[i]);
}
int addLen;
printf("第二次增加数组的长度:");
scanf("%d", &addLen);
//realloc 重新分配内存, 之前开辟的内存指针, 需要扩容或缩小的大小,扩大会
//扩大内存:
//1. 若果当前内存段后面有需要的内存空间,直接扩展,返回原指针
//2. 当前内存段后面的空闲字节不够, 那就使用堆中第一个能满足这一要求的内存区域,把原内存数据copy,然后释放原内存,返回新的指针.
//3. 如果申请失败,返回NULL,原来的指针依然存在
int* p2 = realloc(p,sizeof(int) * (len +addLen));
if (p2 == NULL) {
printf("重新分配内存失败!!!!!!");
}
i = 0;
for (; i < (len + addLen); i++)
{
p2[i] = rand() % 100;
printf("%d, %#x \n", p2[i], &p2[i]);
}
if (p2 != NULL) {
free(p2);
p2 = NULL;
}
getchar();
}
复制代码
- 动态内存的释放 和内存泄漏 不能多次释放,释放完成后置NULL,标志释放完成 内存泄漏 指针重新赋值不等于 释放前内存, 如果不做释放 会发生内存泄漏.
void main() {
//40M
int *p1 = malloc(1024* 1024 * 10 * sizeof(int));
//if (p1 != NULL) {
// free(p1);
// p1 = NULL;
//}
p1 = malloc(1024 * 1024 * 20 * sizeof(int));
if (p1 != NULL) {
free(p1);
p1 = NULL;
}
getchar();
}
复制代码
C 中的字符串
C语言中没有字符串,字符串可以用两种方式表示一种是 字符数组,一种是字符指针 字符数组: 这里的定长字符数组是固定的,可以修改某个index 字符的值,但是一经创建,长度无法改变. 字符指针: 字符指针是不能被修改的. 在C中也有像Java中一样的一个处理String 的类,这个 #include
在线文档:string在线文档
//C中字符串两种创建方式
void main() {
//1. 通过字符数组来 表示一个字符串
//char str[] = {'c','h','i','n', 'a','\0'};
//char str[6] = { 'c','h','i','n', 'a'};
//char str[10] = "china";
可以修改
//str[0] = 's';
//printf("%s \n", str);
//getchar();
//2. 通过字符指针
// 利用字符数组表示的可以修改, 利用指针表示的数组不能被修改. 和java 中类似,string不可变,stringBuffer可变
//内存连续排列
char* str = "how are you?";
//str[0] = 's'; //写入位置时发生访问冲突
printf("%#x \n", str);
printf("%s \n", str);
str += 3;
while (*str) {//取不到就为0, 为0就跳出了
printf("%c", *str);
str++;
}
getchar();
}
复制代码
字符串的处理函数
在线API文档 string在线文档
- 字符串中匹配字符
void main() {
char *str = "I want go to USA!";
printf("%#x\n", str);
char* p = strchr(str, 'w');
if (p)
{
printf("索引位置:%d\n", p - str);
}
else
{
printf("没有找到");
}
system("pause");
}
复制代码
- 字符串的拼接
void main() {
char dest[50];
char *a = "china";
char *b = " is powerful!";
strcpy(dest, a);
strcat(dest, b);
printf("%s\n", dest);
system("pause");
}
复制代码
结构体
结构体相当于Java 中的Object,但是它又有C中的部分独有特性,很多时候我们看jni.h相关的代码或者WebRTC,会看到Google程序员会利用C结构体来模拟实现Java OOP编程的风格,使代码更加清晰,易读,易维护. 把不同的数据类型整合起来成为一个自定义的数据类型
- 定义结构体
struct Man {
//成员
char name1[20];
char* name;
int age;
int(*func)();
void*(*funj)();
};
int test() {
return 1;
}
void main() {
//初始化结构体的变量
//1. 第一种形式
//struct Man m1 = {"Java",20,};
//2. 声明完成后,需要什么自己赋值
struct Man m1;
m1.age = 23;
m1.name = "java";
m1.func = test;
strcpy(m1.name1,"Android"); //name1 是char数组,所以不能直接赋值,只能使用strcpy的方式
printf("%s,%s, %d, %d\n", m1.name,m1.name1, m1.age, m1.func());
getchar();
}
复制代码
- 结构体变量 和 使用
struct Man {
char *name;
int age;
}m1, m2 = {"Java",20};
void main() {
m1.name = "Android";
m1.age = 10;
getchar();
}
//匿名结构体,主要用在控制结构体变量的个数,相当于Java中单例
struct {
int age;
}m3;
void main() {
m3.age = 5;
}
复制代码
- 基本写法
// 结构体的嵌套: 这种写法 和下面的写法 没有任何区别
//struct Student {
// char name[20];
// int age;
// struct Tracher {
// char name[20];
// }t;
//};
struct Teacher {
char name[20];
};
struct Student {
char name[20];
int age;
struct Teacher t;
};
void main() {
//struct Student s1 = { "java",20,{"json"} };
struct Student s1;
s1.age = 10;
strcpy(s1.t.name,"json");
system("pause");
}
复制代码
- 结构体于指针
struct Man {
char name[20];
int age;
};
void main() {
struct Man m1 = {"Java", 20};
struct Man *p = &m1;
printf("%s,%d \n",m1.name,m1.age);
printf("%s,%d \n",(*p).name,(*p).age);
//-> 是(*p).的简写
printf("%s,%d \n", p->name,p->age);
system("pause");
}
复制代码
- 结构体数组
struct Man {
char name[20];
int age;
};
int main() {
struct Man mans[] = { {"Java", 20},{ "Android", 25}};
//遍历结构体数组
//1.
struct Man *p = mans;
for (; p < mans+2; p++)
{
printf("%s, %d\n",p->name, p->age);
}
//2.
int i = 0;
for (; i < 2; i++)
{
printf("%s, %d\n", mans[i].name, mans[i].age);
}
//3.
int i = 0;
for (; i < sizeof(mans) / sizeof(struct Man); i++)
{
printf("%s, %d\n", mans[i].name, mans[i].age);
}
//结构体的大小(字节对齐)
}
复制代码
- 结构体的大小 和字节对齐的概念 结构体的大小该怎么计算呢?,那得先说说什么是字节对齐:
结构体必须满足 最大的数据类型的sizeof的值整除, 所以呢所有的属性呢都会变成8个字节,即使空出来一块.
结构体变量的大小必须是最宽基本数据类型的整数倍. 如果不够,那么他就会在这些成员中间需要添加填充字节.
struct Man {
int age;
double weight;
};
void main() {
struct Man m1 = {20, 170.5};
printf(" %#x,%d,\n", &m1, sizeof(m1));
getchar();
}
复制代码
- 结构体与动态内存分配
struct Man {
char *name;
int age;
};
void main() {
struct Man *m_p = (struct Man*)malloc(sizeof(struct Man) *10);
struct Man *p = m_p;
赋值
p->name = "Jack";
p->age = 20;
p++;
p->name = "Android";
p->age = 21;
struct Man *loop_p = m_p;
for (; loop_p < m_p +2; loop_p++)
{
printf("%s, %d\n",loop_p->name ,loop_p->age);
}
free(m_p);
getchar();
}
复制代码
- 结构体取别名
typedef 取别名:
- 不同的名称代表在干不同的事情,比如:typedef int jint;
- 不同情况下,使用不同的别名
#if defined (_cplusplus);
//typedef _JNIEnv JNIEnv;
//typedef _JavaVM JavaVM;
*/
//struct Man {
// char name[20];
// int age;
//};
//
//typedef struct Man JavaMan;
//typedef struct Man* JM;
//
//typedef int Age; //Age int类型的别名
//typedef int* Ap;//Age int 类型指针的别名;
//结构体取别名
typedef struct Woman {
char name[20];
int age;
} W,*WP;//W是Woman 结构体的别名, WP 是woman 结构体指针的别名
void main() {
int i = 5;
Ap a = &i;
W w1 = {"Java",20};
WP wp1 = &w1;
printf("%s ,%d \n", w1.name, w1.age);
printf("%s ,%d \n",wp1->name, wp1->age);
getchar();
}
复制代码
- 结构体函数指针成员 C语言到最后搞来搞去,写的都是这样的代码:
typedef struct Girl {
char* name;
int age;
void(*sayHi)(char*);
}Girl;
typedef Girl* GirlP; //Girl结构体指针取别名GirlP
//Girl 结构体类似于Java中的类.name和age 类似于属性,sayHi类似于方法.
void sayHi(char* text) {
MessageBox(0,text,"title",0);
}
void rename(GirlP gp) {
gp->name = "Android";
//如果传Girl 类型进来,会重新开辟一个内存空间,用完就销毁了, 所以需要传指针进来
}
void main() {
struct Girl g1 = {"Lucy",18,sayHi};
g1.sayHi("hello");
GirlP gp = &g1;
rename(gp);
gp->sayHi("Byebye!");
getchar();
}
复制代码
联合体 (共用体)
不同类型的变量共同占用一段内存(相互覆盖),联合变量任何情况只有一个内存存在,目的(节省内存). 联合体变量大小= 最大的成员所占的字节数
union MyValue {
int x;
int y;
double z;
};
void main() {
union MyValue d1;
d1.x = 90;
d1.y = 100;
d1.z = 23.8; //最后一次赋值有效
printf("%d,%d,%lf \n",d1.x, d1.y,d1.z);
getchar();
}
复制代码
枚举
枚举 :固定的数据 enumeration(主要用来列举所有的情况,限制值,保证取值的安全性)
enum Day {
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
Sun
};
void main(void) {
//枚举的值必须是括号中出现的值
enum Day d = Mon;
printf("%#x,%d\n",&d,d);
getchar();
}
复制代码
C语言中的文件操作
- 读取和写入文本
//写入
void main() {
char path[] = "D:\\BaiduNetdiskDownload\\test.txt";
//打开
FILE *fp = fopen(path, "w");
char *text = "测试;\n啦啦啦啦啦";
fputs(text, fp);
//关闭流
fclose(fp);
getchar();
}
//读取文本文件
void main()
{
char path[] = "D:\\BaiduNetdiskDownload\\test.txt";
//打开
FILE *fp = fopen(path,"r");
if (fp == NULL) {
printf("文件打开失败...");
return;
}
//读取
char buff[50];
while (fgets(buff,50,fp)) {
printf("%s", buff);
}
//关闭
fclose(fp);
getchar();
}
复制代码
- 文件复制 二进制读取写入文件,计算机的文件存储在物理上都是二进制,文本和二进制之分,其实是一种逻辑之分,c读写文本文件与二进制文件的差别仅仅体现在回车换行,写文本时,每遇到'\n',会将其转成'\r\n'(回车换行),读文本时,每遇到一个'\r\n',都会将其转为'\n'.
void main() {
char path[] = "D:\\BaiduNetdiskDownload\\timg.jpg";
//读的文件 b字符表示操二进制文件binary
FILE *read_fp = fopen(path, "rb");
char write_path[] = "D:\\BaiduNetdiskDownload\\timg1.jpg";
FILE *write_fp = fopen(write_path,"wb");
//复制
int buff[50];
int len = 0;
//fread(buff, sizeof(int), 50, fp) buff,缓冲区的类型的大小,一次读多少,read_fp
while ((len = fread(buff,sizeof(int),50, read_fp)) != 0)
{
fwrite(buff,sizeof(int),len,write_fp);
}
//关闭流
fclose(read_fp);
getchar();
}
复制代码
- 获取文件大小
void main() {
char path[] = "D:\\BaiduNetdiskDownload\\timg.jpg";
FILE *fp = fopen(path, "r");
//该函数可以直接操作文件指针, 操作指针到文件的任何位置,可以用来做断点续传和下载.
//0代表偏移量, SEEK_END 定位的位置根据偏移量
fseek(fp, 0 ,SEEK_END);
//返回当前文件指针,相当于文件开头的偏移量.
long filesize = ftell(fp);
printf("%ld\n", filesize);
getchar();
}
复制代码
- 文件的加密和解密
//解密
void decrypt(char crypt_path[], char decrypt_path[], char password[]) {
//打开文件
FILE *crypt_fp = fopen(crypt_path, "rb");
FILE *decrypt_fp = fopen(decrypt_path, "wb");
//一次读取一个字符
int ch;
int i = 0;
int pwd_len = strlen(password);
while ((ch = fgetc(crypt_fp)) != EOF) {
fputc(ch ^ password[i % pwd_len], decrypt_fp);
if (i == pwd_len)
{
i = 0;
}
else {
i++;
}
}
fclose(crypt_fp);
fclose(decrypt_fp);
}
//加密函数
//异或 : 1^1 = 0 ; 0^0 = 0; 1^0 = 0
void encrypt(char normal_path[], char crypt_path[], char password[]) {
//打开文件
FILE *normal_fp = fopen(normal_path, "rb");
FILE *crypt_fp = fopen(crypt_path, "wb");
//一次读取一个字符
int ch;
int i = 0;
int pwd_len = strlen(password);
while ((ch = fgetc(normal_fp)) != EOF) {
fputc(ch ^ password[i % pwd_len], crypt_fp);
if (i == pwd_len)
{
i = 0;
}
else {
i++;
}
}
fclose(normal_fp);
fclose(crypt_fp);
}
void main() {
char normal_path[] = "D:\\BaiduNetdiskDownload\\timg.jpg";
char crypt_path[] = "D:\\BaiduNetdiskDownload\\timg_crypt.jpg";
char decrypt_path[] = "D:\\BaiduNetdiskDownload\\timg_decrypt.jpg";
//加密
//encrypt(normal_path, crypt_path, "ceshimima");
//解密
decrypt(crypt_path, decrypt_path, "ceshimima");
getchar();
}
复制代码
C语言的预编译 define
这里得说一下为什么要用Visual Studio, VS 可以看到内存情况,可以看到编译日志,.c 编译完成后就是.obj的目标代码.
- 预编译: 为编译做准备工作,完成代码文本的替换工作。
- 编译: 形成目标代码(.obj)---()
- 连接:将目标代码和C函数库连接合并,形成最终的可执行文件
- 执行
/*
例如 这两个头文件互相依赖,需要执行某些事情
A.h
#include "B.h" void printfA();
B.h
#include "A.h" void printfB();
在早期的编译器必须这样才可以解决这种 头文件的互相引用冲突.
#ifndef BH
#define BH
#include "A.h"
void printfB();
#endif // !BH
上面这个这个早期的处理只需要了解一下,新的编译器这样处理就可以了
#pragma once
*/
//2. 定义常数
#define MAX 100
//3. 定义一个宏函数
void wy_com_jni_read() {
printf("read\n");
}
void wy_com_jni_write() {
printf("write\n");
}
#define jni(NAME) wy_com_jni_##NAME();
//4. 日志输出
//__VA_ARGS__ 代表可变参数. FOEMAT前加 ##代表参数,不加也能运行.
#define LOG(LEVEL,FORMAT,...)printf(##LEVEL);printf(##FORMAT,__VA_ARGS__);
#define LOG_I(FORMAT,...) LOG("INFO:",##FORMAT,__VA_ARGS__);
#define LOG_E(FOEMAT,...) printf("ERRO:");printf(##FOEMAT,__VA_ARGS__);
#define LOG_W(FORMAT,...) LOG("WARN:",##FORMAT,__VA_ARGS__);
void main() {
//#include "my.txt" //给my.text 里面就写一个函数myprintf(char *msg){printf("%s",msg);};
//myprintf("java"); //预编译一样可以通过,就是个文本替换.函数一样可以得到执行.
//printfA();
//2. 测试常数
int i = 90;
if (i < MAX) {
printf("比MAX小");
}
//3.调用宏函数,只需要传入name就可以了 自动拼接成需要的函数名并且执行.
jni(read); //调用的时候就看起来比较清爽了.
//4. 测试 自定义LOG
LOG_E("%s,%d\n","lallala",50); //测试自己的
getchar();
}
复制代码
结束语: C语言真的是博大精深,上面的这些例子只是C语言的一角,而我们学习只是为了当时的需求,而我的需求就是能够基本看懂jni.h, 能够使用JNI开发需求App就够了,然而学到最后你会发现,会的越多忘的越快. 所以,如果你当下需要学习C语言,可以把上面的代码敲一遍,虽然笔记是CV大法+一目十行,但是,敲一遍运行一下,提升和找错真的很快.
- github
C语言学习资料
网站资料大全
- C语言中文网-C入门教程
- 菜鸟教程
C语言相关的书籍
- C程序设计语言 《The C Programming Language》
- C语言参考手册 《C Reference Manual》
- C primer plus第6版
- C陷阱与缺陷 《C Traps and Pitfalls》
- 指针的艺术
- 你必须知道的495个c语言问题
- C专家编程 《Expert C Programming》
- 《C标准库》
- 《 C语言接口与实现 》
- 《C语言的科学和艺术》
- 《C程序设计语言》
- 《数据结构与算法分析》
最终彩蛋
C++在C语言的基础上嫁接了面向对象编程工具,面向对象是一门哲学,它通过对语言建模来适应问题,而不是对问题建模以适应语言。C++几乎是C的超集,这意味着任何C程序差不多就是一个C++程序。学习C语言,也相当于学习了许多C++的知识。