内容简介:C基础知识从hello wrld开始物理角度:内存是计算机中必不可少的一部分,是跟CPU沟通的桥梁,计算机中所有的程序都是运行在内存中。
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语言的内存分配
- 栈区(stack)自动分配释放(比如window中一般2 M)
- 堆区 (heap)手动分配释放(可以占用操作系统%80内存)
- 全局区或者静态区
- 字符常量区
- 程序代码区
动态内存分配
//栈内存 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,并没有真正的释放,会造成内存泄漏。
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语言的执行流程
- 编译:形成目标代码(.obj)
- 连接:将目标代码与c函数库连接合并,形成最终的可执行文件
- 执行
预编译阶段,主要为编译做准备工作,完成代码的替换。头文件告诉编译器有这么一个函数,连接器负责到代码中找到这个函数
define(宏定义、宏替换 、预编译指令)
define指令
- 定义标示(#ifdef __cplusplus 标识支持C++语法)
- 定义常数(#define MAX 100)
-
定义宏函数,简化比较麻烦的函数
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文件
以上所述就是小编给大家介绍的《C基础知识》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。