C基础知识

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 = ≷
	//传入指针改名。使用变量是无法改名的。
	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文件

原文 

https://chsmy.github.io/2019/05/12/technology/C基础知识/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » C基础知识

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址