44

C基础知识

 4 years ago
source link: https://chsmy.github.io/2019/05/12/technology/C基础知识/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

C基础知识

Hello World

从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的内存组成:

  • 运行时系统分配空间:栈,堆
  • 编译时编译器分配的空间:BSS段(存放全局的成员变量),数据段(一段数据),代码段(转化后的汇编指令)

c语言的内存分配

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

动态内存分配

//栈内存
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,并没有真正的释放,会造成内存泄漏。
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");

}

typedef 类型取别名

取别名好处:让代码简洁,不同情况下使用不同的别名,不同的名称代表干不同的事情

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

c中的文件操作

读取文件

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

C语言的执行流程

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

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

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

define指令

  1. 定义标示(#ifdef __cplusplus 标识支持C++语法)
  2. 定义常数(#define MAX 100)
  3. 定义宏函数,简化比较麻烦的函数
    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命令编译

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

动态库:.so/.dll

静态库:.a/.lib

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK