C基础知识
从hello wrld开始
#include <stdio.h> printf("hello world/n"); system("pause");
物理角度:内存是计算机中必不可少的一部分,是跟CPU沟通的桥梁,计算机中所有的程序都是运行在内存中。
逻辑角度:内存是一块具备随机访问能力,支持读、写操作,用来存放程序运行中产生的数据的区域。
内存:位(bit),字节(1byte=8bit),KB(1KB=1024字节)MB(1MB=1024KB)
内存编址:计算机中内存按字节编址,每个地址的存储单元可以存放一个字节(8bit)的数据,CPU通过内存地址回去指令和数据,并不关心这个地址所代表的控件具体在什么位置,怎么分布,因为硬件的设计保证一个地址对应着一个具体的空间。
内存地址:通常使用16进制的数值表示,指向内存中某一个区域。
静态内存分配
int a[1024 * 1024 * 10];
C的内存组成:
c语言的内存分配
动态内存分配
//栈内存 void stackFun(){ int a[1024] } //堆内存 void heapFun(){ //申请内存空间 int *p = malloc(1024*1024*4) //释放内存 free(p) } //动态内存分配(相当于java中的集合) int len = 5; //申请内存,申请完之后,p就变成一个数组 int *p = malloc(len*sizeof(int)); //也可以用calloc(len,sizeof(int)); //给数组赋值 int i = 0; for(; i<len-1 ;i++){ p[i] = rand()%100 } //释放内存 free(p); p=NULL; //使用realloc 重新分配内存 //第一个参数:原来指针的内存指针 //第二个参数:内存扩大之后的总大小 int addLen = 5; int* p2 = realloc(p, sizeof(int) * (len + addLen));
重新分配内存的两种情况
内存分配需要注意的几个细节
void main(){ //给p1赋值 int* p1 = malloc(1024 * 1024 * 10 * sizeof(int)); //先释放内存 free(p1); //打印一下可以看到,释放后p1并不为空 printf("%#x/n",p1); p1 = NULL; //在给p1重新赋值 p1 = malloc(1024 * 1024 * 10 * sizeof(int) * 2); free(p1); p1 = NULL; }
int %d 字节数:4 short %d 字节数:2 long %ld 字节数:4(跟java不一样) float %f 字节数:4 double %lf 字节数:8 char %c 字节数:1 %x 十六进制 %0 八进制 %s 字符串
指针存储的是变量的内存地址
指针有类型,地址没有类型
比如int类型的指针不能赋值为double类型的指针,因为指针只是指向一个地址的首位。具体走多少需要看类型。
void main() { int i = 100; //p是int类型的指针,代表这个int类型的值的内存地址 int *p = &i; printf("%#x/n", p); printf("%#x/n", &p); printf("%#x/n", &i); printf("%#x/n", i); //p是指针,*p代表取地址的值 system("pause"); }
指针保存的是变量的地址,它保存的这个地址变量也可以使一个指针变量。
int a = 50; //p1上保存的a的地址 int* p1 = &a; //p2上保存的p1的地址 int** p2 = &p1; //通过二级指针改变a的值 **p2 = 100;
一般用在数组遍历,指针加一,就是向前移动一个数据类型的字节。比如是int类型的,移动4位,double类型的移动8位
//数组在内存中是连续存储的 int ids[] = { 78, 90, 23, 65, 19 }; //数组变量名:ids就是数组的首地址 //ids,&ids,&ids[0]的值是一样的 printf("%#x/n",ids); printf("%#x/n",&ids); printf("%#x/n",&ids[0]); //指针变量 int *p = ids; printf("%d/n",*p); //指针的加法 //p++向前移动sizeof(数据类型)个字节 p++; printf("p的值:%#x/n", p); //p--; printf("%d/n", *p);
通过指针给数组赋值
int arr[5]; int i = 0; for (; i < 5; i++){ arr[i] = i; }
指针数组(数组里面存放的是指针)
int *p[3];
数组指针(行指针)
int (*p)[n]
优先级:()>[]>* ,所以首先p是一个指针,指向一个整型数组,这个数组的长度是n,也可以说是p的步长,也就是说执行p+1的时候,p要跨过n个整型的长度。
当浏览一个图片的时候,可以使用数组指针来读取。
int a[3][4]//定义一个3行4列的二维数组 int (*p)[4]//指针数组指向含有4个元素的以为数组 p=a//将该二维数组的首地址赋值为p,也就是a[0]或者a[0][0] p++ //执行该语句之后,跨过一行比如从a[0][]指向a[1][]
变量名是对内存空间上的一段数据的抽象,我们可以对p存的内存地址的变量进行操作
也可以定义一个方法,参数就是一个变量的指针,调用方法的时候,传入指针,就可改变这个变量的值。
void change(int* p){ *p = 300; } void main() { int i = 100; printf("i的值为:%d/n", i); //p是i的指针 int *p = &i; //通过指针赋值 *p = 200; printf("i的值为:%d/n", i); //change(p); change(&i); system("pause"); }
指针函数 是一个函数 ,返回一个指针
//void 是无符号类型,类比于java中的Object int* int_add_func(void* wParam) { printf("指针函数/n"); int b = 10; int *p = &b; //指针函数返回一个指针 return p; } void main() { int a = 10; int_add_func(&a); system("pause"); }
函数指针 是一个变量 ,是一个指向函数的指针变量。
回调的时候经常用到
//(*funcp)要用括号括起来,括号代表优先级 void(*funcp)(int* a, int* b); void point_func(int* a, int* b) { *a = 200; printf("函数指针/n"); } //main函数中,给这个函数指针赋值, //**然后就可以通过它调用这个函数了**。 void main() { int b = 20; funcp = point_func; funcp(&a, &b); printf("a值 %d", a); system("pause"); } ----------------------------------- int add(int a,int b){ return a + b; } int minus(int a,int b){ return a - b; } void msg(int(*func_p)(int a, int b), int m, int n){ printf("执行一段代码.../n"); printf("执行回调函数.../n"); int r = func_p(m, n); printf("执行结果:%d/n",r); } void main(){ //加法 //msg(add,50,10); //减法 //msg(minus,50,10); } //定义两个方法 add,minus。msg这个方法中,需要传入一个 //函数指针int(*func_p)(int a, int b)和两个值 //只要是返回int值,传入两个参数的这种方法, //都可以传入到msg方法中计算。
使用字符数组来存储字符串
//'/0'代表结束 char str[] = {'a','b','c','d','e','/0'}; char str[6] = {'a','b','c','d','e'}; char str[10]="china"; str[0] = 's';
//内存连续排列 char *str = "hello world"; //不能修改,下一行代码会报错 str[0] = 's'; //使用指针加法,截取字符串 str += 3; while (*str){ printf("%c",*str); str++; } //字符串拼接 void main(void){ char dest[50]; char *a = "china"; char *b = " is powerful!"; strcpy(dest, a); strcat(dest, b); printf("%s/n", dest); system("pause"); } //查找一个字符的位置 void main(void){ char *str = "I want go to USA!"; printf("%#x/n", str); //U元素的指针 //str+3 char* p = strchr(str, 'w'); if (p){ printf("索引位置:%d/n", p - str); } else{ printf("没有找到"); } system("pause"); }
操作字符串的在线API文档: http://www.kuqin.com/clib/string/strcpy.html
相当于java中的类。把不同的数据类型整合起来
几种结构体的写法
struct Man { char name[20]; int age; }; //s1 s2是结构体的变量名 struct student { char name[20]; int age; } s1 ,s2; //匿名结构体 相当于单例 struct { char name[20]; int age; } m1; //赋值方式如下 void main(){ struct Man man; man.age = 10; strcpy(man.name,"chs"); s1.age = 11; strcpy(s1.name, "lr"); m1.age = 12; strcpy(m1.name, "czg"); system("pause"); }
结构体嵌套
//第一种写法 struct student { char name[20]; int age; } s1 ,s2; struct teacher { char name[20]; struct student s; } t; //第二种写法 struct teacher { char name[20]; struct student { char name[20]; int age; } s; }; //赋值方式 void main(){ strcpy(t.name, "czg"); t.s.age = 13; strcpy(t.s.name, "cxh"); system("pause"); }
结构体指针
struct student { char name[20]; int age; }; void main(){ struct student s = {"czl",12}; struct student *p = &s; //使用指针赋值 p->age = 20; strcpy(p->name, "xc"); //使用变量赋值 s.age = 20; strcpy(s.name, "xc"); system("pause"); }
结构体数组和指针
struct student { char name[20]; int age; }; void main(){ struct student stus[] = { {"Jack",20}, {"Rose", 19} }; //遍历结构体数组 //第一种方式,使用指针 struct student *p = stus; for (; p< stus + 2;p++) { printf("%s,%d/n", p->name, p->age); } //第二种方式,使用变量 int i = 0; for (; i < sizeof(stus) / sizeof(struct student); i++) { printf("%s,%d/n", stus[i].age, stus[i].name); } system("pause"); }
结构体的大小
struct Man{ int age; double weight; }; void main(){ struct Man m1 = {20,55.0}; printf("%#x,%d/n", &m1,sizeof(m1)); getchar(); }
上面的结构体有两个变量一个int类型一个double类型,通过打印可以看到,该结构体的大小是16。
这就是结构体的内存对齐,结构体的大小,是其内部最大数据类型的整数倍,如果在加一个int类型的变量,那大小就是24。这样做的原因是为了提升效率,以空间换时间。
struct Man { int age; char *name; }; void main(){ //开辟一块内存 struct Man *p = (struct Man*)malloc(sizeof(struct Man) * 10); //赋值 struct Man *mp = p; mp->age = 18; mp->name = "lily"; //循环遍历 struct Man *lop = p; for (; lop < p + 2;lop++) { printf("%s,%d/n", lop->name, lop->age); } system("pause"); }
取别名好处:让代码简洁,不同情况下使用不同的别名,不同的名称代表干不同的事情
//Age int类型指针的别名 typedef int Age; //Age int类型指针的别名 typedef int* Ap; struct Man{ char name[20]; int age; }; //给结构体取别名 typedef struct Man M; typedef struct Man* MP; void main(){ int i = 5; Ap p = &i; //结构体变量 M m1 = {"Rose",20}; //结构体指针 MP mp1 = &w1; printf("%s,%d/n", m1.name, m1.age); printf("%s,%d/n", mp1->name, mp1->age); getchar(); }
Girl了类似于java中的类,sayHi类似于java中的方法。
struct Girl{ char *name; int age; //函数指针 void(*sayHi)(char*); }; struct Girl { char *name; int age; //函数指针 void(*sayHi)(char*); }; void sayHi(char *text) { printf(text); } void main(){ struct Girl gl; gl.age = 18; gl.name = "lily"; gl.sayHi = sayHi; gl.sayHi("hello"); system("pause"); }
给Gril类取别名。在c中大多数情况下都是操作的指针
typedef struct Girl { char *name; int age; //函数指针 void(*sayHi)(char*); } Girl; //定义一个Girl的指针类型 typedef Girl *GirlP; void sayHi(char *text) { printf(text); } //改名方法需要传入指针类型 void rename(GirlP gp1) { gp1->name = "Lily"; } void main(){ Girl gl; gl.age = 18; gl.name = "lily"; gl.sayHi = sayHi; gl.sayHi("hello"); //拿到指针 GirlP gpl = ≷ //传入指针改名。使用变量是无法改名的。 rename(gpl); system("pause"); }
共用体是一种特殊的数据类型,允许相同的内存位置存储不同的数据类型,比如定义一个多成员的共用体,它同一个时间只能有一个成员有值。
目的就是为了节省内存,共用体的大小取决于最大的类型的大小。
union MyValue{ int x; short 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); system("pause"); }
上面的例子通过打印之后看到,只有最后一个d1.z有值。
enum Day { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }; void main() { //枚举的值,必须是括号中的值 enum Day d = Monday; printf("%#x,%d/n", &d, d); getchar(); }
读取文件
void main() { char *path = "C://Users//83734//Desktop//2.1.txt"; //r代表只读 FILE *fp = fopen(path, "r"); if (fp == NULL) { printf("文件打开失败..."); return; } //缓冲 char buff[50]; while (fgets(buff, 50, fp)) { printf("%s", buff); } fclose(fp); system("pause"); }
写入文件
void main() { char *path = "C://Users//83734//Desktop//3.1.txt"; //打开 w代表写 FILE *fp = fopen(path, "w"); char *text = "你好 世界"; fputs(text, fp); //关闭流 fclose(fp); system("pause"); }
读取二进制文件并复制
void main() { char *path = "C://Users//83734//Desktop//color_filter.jpg"; char *path_new = "C://Users//83734//Desktop//color_filter_new.jpg"; //读的指针 rb代表读取二进制 FILE *read_fp = fopen(path, "rb"); //写的指针 wb代表写入二进制 FILE *write_fp = fopen(path_new, "wb"); //缓冲区 int buff[50]; //每次读取到的数据的长度 int len = 0; while ((len = fread(buff,sizeof(int),50,read_fp))!=0) { fwrite(buff,sizeof(int),len,write_fp); } //关闭流 fclose(read_fp); fclose(write_fp); system("pause"); }
c读写文本文件和二进制文件的差别,只在回车换行符上面,写文本的时候每遇到一个/n就会将其转换成/r/n,读文本的时候,每遇到一个/r/n就会将其转换成/n。
获取一个文件的大小,可以通过fseek和ftell
void main() { char *path = "C://Users//83734//Desktop//color_filter.jpg"; //读的指针 rb代表读取二进制 FILE *read_fp = fopen(path, "rb"); //重新定位文件指针 //SEEK_END文件末尾,0偏移量 fseek(read_fp, 0, SEEK_END); //返回当前的文件指针,相对于文件开头的位移量 long filesize = ftell(read_fp); printf("%d/n", filesize); fclose(read_fp); system("pause"); }
文件的加解密
可以读取每个文件的字符,然后给每个字符做异或运算,解密的时候在做一次异或运算
//加密 void crpypt(char normal_path[], char crpypt_path[]) { //打开文件 FILE *normal_fp = fopen(normal_path, "r"); FILE *crypt_fp = fopen(crpypt_path, "w"); //一次读取一个字符 int ch; while ((ch = fgetc(normal_fp)) != EOF) { //End of File //写入(异或运算) fputc(ch ^ 3, crypt_fp); } //关闭 fclose(crypt_fp); fclose(normal_fp); } //解密 void decrpypt(char crpypt_path[], char decrpypt_path[]) { //打开文件 FILE *normal_fp = fopen(crpypt_path, "r"); FILE *crypt_fp = fopen(decrpypt_path, "w"); //一次读取一个字符 int ch; while ((ch = fgetc(normal_fp)) != EOF) { //End of File //写入(异或运算) fputc(ch ^ 3, crypt_fp); } //关闭 fclose(crypt_fp); fclose(normal_fp); } void main() { char *path = "C://Users//83734//Desktop//2.1.txt"; char *path_c = "C://Users//83734//Desktop//2.1.1.txt"; char *path_de = "C://Users//83734//Desktop//2.1.2.txt"; //crpypt(path,path_c); decrpypt(path_c, path_de); }
前面的加解密都是跟一个3异或,有时候我们可以使用一个字符串作为密码比如“abcd”,读取到的每一个字符循环跟字符串中的字符异或。
//加密 void crpypt(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){ //End of File //写入(异或运算) fputc(ch ^ password[i % pwd_len], crypt_fp); i++; } //关闭 fclose(crypt_fp); fclose(normal_fp); } //解密 void decrpypt(char crypt_path[], char decrypt_path[],char password[]){ //打开文件 FILE *normal_fp = fopen(crypt_path, "rb"); FILE *crypt_fp = fopen(decrypt_path, "wb"); //一次读取一个字符 int ch; int i = 0; //循环使用密码中的字母进行异或运算 int pwd_len = strlen(password); //密码的长度 while ((ch = fgetc(normal_fp)) != EOF){ //End of File //写入(异或运算) fputc(ch ^ password[i % pwd_len], crypt_fp); i++; } //关闭 fclose(crypt_fp); fclose(normal_fp); } void main(){ char *normal_path = "C://Users//83734//Desktop//color_filter.jpg"; char *crypt_path = "C://Users//83734//Desktop//color_filter_c.jpg"; char *decrypt_path = "C://Users//83734//Desktop//color_filter_de.jpg"; //crpypt(normal_path, crypt_path,"abcd"); decrpypt(crypt_path, decrypt_path,"abcd"); }
预编译阶段,主要为编译做准备工作,完成代码的替换。头文件告诉编译器有这么一个函数,连接器负责到代码中找到这个函数
define指令
void dn_com_jni_read(){ printf("read/n"); } void dn_com_jni_write(){ printf("write/n"); } //定义宏函数 NAME是参数 #define jni(NAME) dn_com_jni_##NAME(); void main(){ //直接调用定义的宏函数 jni(write);//替换:dn_com_jni_write(); getchar(); }
库可以通过gcc命令编译
//动态库 gcc -shared -fPIC -o libtest.so test.c //静态库 gcc -static -fPIC -o libtest.so test.c
动态库:.so/.dll
静态库:.a/.lib
动态库类似于android中的.jar文件
静态库类似于andorid中的.arr文件