C基础知识

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语言的内存分配

  1. 栈区(stack)自动分配释放(比如window中一般2 M)
  2. 堆区 (heap)手动分配释放(可以占用操作系统%80内存)
  3. 全局区或者静态区
  4. 字符常量区
  5. 程序代码区

动态内存分配

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));

重新分配内存的两种情况

  1. 缩小内存,缩小的那一部分会丢失
  2. 扩大内存,如果当前内存段后面有需要的内存空间,直接扩展这段内存空间,realloc返回原指针
  3. 扩大内存,如果当前内存段后面空闲不够,那么就从堆中找到第一个可以满足内存需求的一块内存,把原来的数据复制过来,并释放原来的内存,返回新的地址
  4. 扩大内存,如果申请失败,返回NULL,原来的指针仍然有效。

内存分配需要注意的几个细节

  1. 不能多次释放
  2. 释放完成之后,给指针置NULL,标志释放完成
  3. 如果不是使用realloc重新赋值而是malloc给p重新赋值之后,在free,并没有真正的释放,会造成内存泄漏。
1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

基本数据类型

1
2
3
4
5
6
7
8
9
int %d               字节数:4
short %d 字节数:2
long %ld 字节数:4(跟java不一样)
float %f 字节数:4
double %lf 字节数:8
char %c 字节数:1
%x 十六进制
%0 八进制
%s 字符串

指针

指针存储的是变量的内存地址

指针有类型,地址没有类型

比如int类型的指针不能赋值为double类型的指针,因为指针只是指向一个地址的首位。具体走多少需要看类型。

1
2
3
4
5
6
7
8
9
10
11
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");
}

多级指针

指针保存的是变量的地址,它保存的这个地址变量也可以使一个指针变量。

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
5
int 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
4
int 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
16
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");
}

函数

指针函数是一个函数,返回一个指针

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
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
//(*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方法中计算。

字符串

使用字符数组来存储字符串

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
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
//内存连续排列
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中的类。把不同的数据类型整合起来

几种结构体的写法

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
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");

}

结构体嵌套

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
//第一种写法
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");

}

结构体指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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");
}

结构体数组和指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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");

}

结构体的大小

1
2
3
4
5
6
7
8
9
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。这样做的原因是为了提升效率,以空间换时间。

结构体动态内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");

}

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
26
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中大多数情况下都是操作的指针

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
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 = &gl;
//传入指针改名。使用变量是无法改名的。
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
22
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有值。

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Day
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};

void main() {
//枚举的值,必须是括号中的值
enum Day d = Monday;
printf("%#x,%d\n", &d, d);
getchar();
}

c中的文件操作

读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
}

写入文件

1
2
3
4
5
6
7
8
9
10
11
12
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");
}

读取二进制文件并复制

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和ftell

1
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语言的执行流程

  1. 编译:形成目标代码(.obj)
  2. 连接:将目标代码与c函数库连接合并,形成最终的可执行文件
  3. 执行

预编译阶段,主要为编译做准备工作,完成代码的替换。头文件告诉编译器有这么一个函数,连接器负责到代码中找到这个函数

define(宏定义、宏替换 、预编译指令)

define指令

  1. 定义标示(#ifdef __cplusplus 标识支持C++语法)
  2. 定义常数(#define MAX 100)
  3. 定义宏函数,简化比较麻烦的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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();
    }

c中的库

库可以通过gcc命令编译

1
2
3
4
//动态库
gcc -shared -fPIC -o libtest.so test.c
//静态库
gcc -static -fPIC -o libtest.so test.c

动态库:.so/.dll

静态库:.a/.lib

动态库类似于android中的.jar文件

静态库类似于andorid中的.arr文件

# c/c++

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×