C基础知识
Hello World
从hello wrld开始1
2
3
4#include <stdio.h>
printf("hello world\n");
system("pause");
内存
物理角度:内存是计算机中必不可少的一部分,是跟CPU沟通的桥梁,计算机中所有的程序都是运行在内存中。
逻辑角度:内存是一块具备随机访问能力,支持读、写操作,用来存放程序运行中产生的数据的区域。
内存:位(bit),字节(1byte=8bit),KB(1KB=1024字节)MB(1MB=1024KB)
内存编址:计算机中内存按字节编址,每个地址的存储单元可以存放一个字节(8bit)的数据,CPU通过内存地址回去指令和数据,并不关心这个地址所代表的控件具体在什么位置,怎么分布,因为硬件的设计保证一个地址对应着一个具体的空间。
内存地址:通常使用16进制的数值表示,指向内存中某一个区域。
动态内存分配
静态内存分配1
int a[1024 * 1024 * 10];
C的内存组成:
- 运行时系统分配空间:栈,堆
- 编译时编译器分配的空间:BSS段(存放全局的成员变量),数据段(一段数据),代码段(转化后的汇编指令)
c语言的内存分配
- 栈区(stack)自动分配释放(比如window中一般2 M)
- 堆区 (heap)手动分配释放(可以占用操作系统%80内存)
- 全局区或者静态区
- 字符常量区
- 程序代码区
动态内存分配1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30//栈内存
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));
重新分配内存的两种情况
- 缩小内存,缩小的那一部分会丢失
- 扩大内存,如果当前内存段后面有需要的内存空间,直接扩展这段内存空间,realloc返回原指针
- 扩大内存,如果当前内存段后面空闲不够,那么就从堆中找到第一个可以满足内存需求的一块内存,把原来的数据复制过来,并释放原来的内存,返回新的地址
- 扩大内存,如果申请失败,返回NULL,原来的指针仍然有效。
内存分配需要注意的几个细节
- 不能多次释放
- 释放完成之后,给指针置NULL,标志释放完成
- 如果不是使用realloc重新赋值而是malloc给p重新赋值之后,在free,并没有真正的释放,会造成内存泄漏。
1 | void main(){ |
基本数据类型
1 | int %d 字节数:4 |
指针
指针存储的是变量的内存地址
指针有类型,地址没有类型
比如int类型的指针不能赋值为double类型的指针,因为指针只是指向一个地址的首位。具体走多少需要看类型。1
2
3
4
5
6
7
8
9
10
11void 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");
}
多级指针
指针保存的是变量的地址,它保存的这个地址变量也可以使一个指针变量。1
2
3
4
5
6
7 int a = 50;
//p1上保存的a的地址
int* p1 = &a;
//p2上保存的p1的地址
int** p2 = &p1;
//通过二级指针改变a的值
**p2 = 100;
指针的运算
一般用在数组遍历,指针加一,就是向前移动一个数据类型的字节。比如是int类型的,移动4位,double类型的移动8位1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 //数组在内存中是连续存储的
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);
通过指针给数组赋值1
2
3
4
5int arr[5];
int i = 0;
for (; i < 5; i++){
arr[i] = i;
}
指针数组 (数组里面存放的是指针)
1 | int *p[3]; |
数组指针(行指针)
1 | int (*p)[n] |
优先级:()>[]>* ,所以首先p是一个指针,指向一个整型数组,这个数组的长度是n,也可以说是p的步长,也就是说执行p+1的时候,p要跨过n个整型的长度。
当浏览一个图片的时候,可以使用数组指针来读取。1
2
3
4int a[3][4]//定义一个3行4列的二维数组
int (*p)[4]//指针数组指向含有4个元素的以为数组
p=a//将该二维数组的首地址赋值为p,也就是a[0]或者a[0][0]
p++ //执行该语句之后,跨过一行比如从a[0][]指向a[1][]
变量名
变量名是对内存空间上的一段数据的抽象,我们可以对p存的内存地址的变量进行操作
也可以定义一个方法,参数就是一个变量的指针,调用方法的时候,传入指针,就可改变这个变量的值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void 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");
}
函数
指针函数是一个函数,返回一个指针1
2
3
4
5
6
7
8
9
10
11
12
13
14//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");
}
函数指针是一个变量,是一个指向函数的指针变量。
回调的时候经常用到
1 | //(*funcp)要用括号括起来,括号代表优先级 |
字符串
使用字符数组来存储字符串1
2
3
4
5//'\0'代表结束
char str[] = {'a','b','c','d','e','\0'};
char str[6] = {'a','b','c','d','e'};
char str[10]="china";
str[0] = 's';
字符指针
1 | //内存连续排列 |
操作字符串的在线API文档:http://www.kuqin.com/clib/string/strcpy.html
结构体
相当于java中的类。把不同的数据类型整合起来
几种结构体的写法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30struct 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");
}
结构体嵌套
1 | //第一种写法 |
结构体指针1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16struct 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");
}
结构体数组和指针1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20struct 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");
}
结构体的大小1
2
3
4
5
6
7
8
9struct 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。这样做的原因是为了提升效率,以空间换时间。
结构体动态内存分配
1 | struct Man { |
typedef 类型取别名
取别名好处:让代码简洁,不同情况下使用不同的别名,不同的名称代表干不同的事情1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25//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中的方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26struct 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中大多数情况下都是操作的指针1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30typedef 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");
}
共用体(联合体)
共用体是一种特殊的数据类型,允许相同的内存位置存储不同的数据类型,比如定义一个多成员的共用体,它同一个时间只能有一个成员有值。
目的就是为了节省内存,共用体的大小取决于最大的类型的大小。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22union 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有值。
枚举
1 | enum Day |
c中的文件操作
读取文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void 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");
}
写入文件1
2
3
4
5
6
7
8
9
10
11
12void 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");
}
读取二进制文件并复制1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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和ftell1
2
3
4
5
6
7
8
9
10
11
12
13
14
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");
}
文件的加解密
可以读取每个文件的字符,然后给每个字符做异或运算,解密的时候在做一次异或运算1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39//加密
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”,读取到的每一个字符循环跟字符串中的字符异或。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48//加密
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");
}
C语言的执行流程
- 编译:形成目标代码(.obj)
- 连接:将目标代码与c函数库连接合并,形成最终的可执行文件
- 执行
预编译阶段,主要为编译做准备工作,完成代码的替换。头文件告诉编译器有这么一个函数,连接器负责到代码中找到这个函数
define(宏定义、宏替换 、预编译指令)
define指令
- 定义标示(#ifdef __cplusplus 标识支持C++语法)
- 定义常数(#define MAX 100)
- 定义宏函数,简化比较麻烦的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14void 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();
}
c中的库
库可以通过gcc命令编译
1 | //动态库 |
动态库:.so/.dll
静态库:.a/.lib
动态库类似于android中的.jar文件
静态库类似于andorid中的.arr文件