一、基本数据类型
基本数据类型
数据类型 | 字符串模板格式化输出 | sizeof()字节 |
---|
int | %d | 4 | short | %d | 2 | long | %ld | 8 | float | %f | 4 | double | %lf | 8 | char | %c | 1 |
进制格式化输出
sizeof判断字节长度
printf("int 占%d字节\n", sizeof(int));
printf("char 占%d字节\n", sizeof(char));
printf("float 占%d字节\n", sizeof(float));
打印函数printf,
int i;
scanf("%d", &i);
输入函数scanf
printf("i的值为:%d\n", i);
系统暂停
system("pause");
二、指针
指针存储的是变量的内存地址。
2.1、变量指针
*:指针的数据类型与变量一致,即变量int i对应int *p
&:取地址操作符,获取变量地址
int i = 90;
int *p = &i;
printf("%#x\n", p);
指针赋值,也叫变量间接赋值。
i = 89;
*p = 89;
空指针NULL。
空指针的默认值为0
int *p = NULL;
printf("%#x\n", p);
2.2、多级指针
指针保存的是变量的地址
int a = 50;
int* p1 = &a;
int** p2 = &p1;
printf("p1:%#x\n", p1);
printf("p2:%#x\n", p2);
printf("*p2:%#x\n", *p2);
多级指针赋值
**p2 = 90;
2.3、数组
ids:数组名
&ids[0]:数组第一个元素地址
&ids:数组地址
都相等
int ids[] = { 78, 90, 23, 65, 19 };
printf("%#x\n", ids);
printf("%#x\n", &ids[0]);
printf("%#x\n", &ids);
指针运算p++、p–:移动sizeof(数据类型)个字节
int* p = ids;
p++;
printf("p的值:%#x\n", p);
printf("p指向:%d\n", *p);
通过指针给数组赋值
int uids[5];
int* p = uids;
for (int i=0; p < uids + 5; p++,i++)
{
*p = i;
}
等价于(在最古老的C语言里是没有中括号array[i]的)
int uids[5];
for (int i = 0; i < 5; i++)
{
uids[i] = i;
}
2.4、函数指针
函数名即函数地址
int msg(char* msg, char* title)
{
printf("title:%s,msg:%s\n",title,msg);
return 0;
}
int main(int argc, const char * argv[]) {
printf("%#x\n", msg);
printf("%#x\n", &msg);
return 0;
}
函数指针
int (*fun_p)(char* msg, char* title) = msg;
fun_p("content", "title");
msg("content","title");
函数指针作为入参。
下图msg函数入参是minus函数、10、20
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");
int r = func_p(m, n);
printf("执行结果:%d\n", r);
}
void main()
{
msg(minus, 10, 20);
}
2.5、小节
案例:用随机数生成一个数组,写一个函数查找最小的值,并返回最小数的地址,在主函数中打印出来
随机数生成
srand((unsigned)time(NULL));
rand() % 100;
整个算法
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int* getMinPointer(int ids[], int len)
{
int i = 0;
int* p = ids;
for (; i < len; i++)
{
if (ids[i] < *p)
{
p = &ids[i];
}
}
return p;
}
int* generateArray(){
int ids[10];
srand((unsigned)time(NULL));
for (int i = 0; i < 10; i++) {
ids[i] = rand() % 100;
printf("%d\n", ids[i]);
}
return ids;
}
void main()
{
int* ids=generateArray();
int* p = getMinPointer(ids, sizeof(ids) / sizeof(int));
printf("%#x,%d\n",p, *p);
}
2.6、指针与数据的几种写法
对于数组:
a+i
*(a+i)
推导如下
int a[] = { 78, 34, 73, 25, 80, 90 };
验证
int a[] = { 78, 34, 73, 25, 80, 90 };
int* p = a;
for (int i = 0; i < 6; i++)
{
printf("%d,%#x\n", a[i], &a[i]);
printf("%d,%#x\n", *(a + i), a + i);
printf("%d,%#x\n", p[i], &p[i]);
printf("%d,%#x\n", *(p + i), p + i);
}
2.7、二维数组
遍历输出
void main()
{
int a[2][3] = { 95,82,56,17,29,30 };
for(int i=0;i<2;i++){
for (int j=0; j<3; j++) {
printf("%d,%#x ",a[i][j],&a[i][j]);
}
printf("\n");
}
}
二维数组a、*a、&a的区别:
| 代表含义 | 内存地址 | Sizeof大小 |
---|
a | 行指针,是数组第一行的指针 | 0xbfeff2a0 | 12 | *a | 数组的第一行第一个元素的指针 | 0xbfeff2a0 | 4 | &a | 一个指向二维数组的指针 | 0xbfeff2a0 | 24 |
验证
printf("%#x,%#x,%#x\n", a, &a, *a);
printf("%d,%d,%d\n",sizeof(*a),sizeof(*&a),sizeof(**a));
指针移动
printf("%#x,%#x\n", a, a + 1);
printf("%#x,%#x\n", *a, *a + 1);
printf("%#x,%#x\n", *(a + 1), *(a + 1) + 1);
取数组的第2行,第3个元素
printf("%d\n", a[1][2]);
printf("%d\n", *(*(a + 1)+2));
三、动态内存分配
3.1、C语言内存分配:
1.栈区(stack) 自动分配,释放 2.堆区(heap) 程序员手动分配释放,操作系统80%内存 3.全局区或静态区 4.字符常量区 5.程序代码区
3.2、栈区(stack)内存分配
使用基本数据类型关键字创建即可。
int a[1024 * 1024 * 10];
3.3、堆区(heap)内存分配
使用malloc、realloc关键字开辟内存
int* p = malloc(len * sizeof(int));
int* p2 = realloc(p, sizeof(int) * (len + addLen));
静态内存分配,分配内存大小的是固定,问题:1.很容易超出栈内存的最大值 2.为了防止内存不够用会开辟更多的内存,容易浪费内存 动态内存分配,在程序运行过程中,动态指定需要使用的内存大小,手动释放,释放之后这些内存还可以被重新使用(类似活水)
3.3.1、malloc开辟
循环开辟释放40M内存
#include <unistd.h>
void main()
{
while (1) {
sleep(3);
int* p = malloc(1024 * 1024 * 10 * sizeof(int));
printf("%#x\n", p);
sleep(3);
free(p);
sleep(3);
}
}
练习:创建一个数组,动态指定数组的大小
void main()
{
int a[10];
int len;
printf("输入数组的长度:");
scanf("%d", &len);
int* p = malloc(len * sizeof(int));
srand((unsigned)time(NULL));
for (int i = 0; i < len; i++)
{
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
}
free(p);
}
3.3.2、realloc扩容
重新分配内存的两种情况:
缩小,缩小的那一部分数据会丢失
扩大,(连续的)
1.如果当前内存段后面有需要的内存空间,直接扩展这段内存空间,realloc返回原指针
2.如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据库释放掉,返回新的内存地址
3.如果申请失败,返回NULL,原来的指针仍然有效
int addLen;
printf("输入数组增加的长度:");
scanf("%d", &addLen);
int* p2 = realloc(p, sizeof(int) * (len + addLen));
if (p2 == NULL)
{
printf("重新分配失败,世界那么大,容不下我。。。");
}
i = 0;
for (int i = 0; i < len + addLen; i++)
{
p2[i] = rand() % 100;
printf("%d,%#x\n", p2[i], &p2[i]);
}
if (p2 != NULL) {
free(p2);
p2 = NULL;
}
四、字符串
4.1、字符串存储方式
在java中字符串String是不可修改的,只能重新被复制。
在c中字符串有两种表达式:
字符数组:可修改
字符指针:不可修改
4.1.1、字符数组
字符数组表示字符串主要有这三种方式
char str[] = {'c','h','i','n','a','\0'};
char str[6] = { 'c','h','i','n','a' };
char str[10] = "china";
str[0] = 's';
printf("%s,%#x\n", str,str);
对于不指定长度的字符串数组初始化,会读取错误
char str[] = {'c','h','i','n','a'};
4.1.2、字符指针
char* str = "how are you?";
*str = 'y';
使用指针加法,截取字符串
str += 3;
while (*str) {
printf("%c",*str);
str++;
}
不能使用%s输出
printf("%s,%#x\n", str,str);
4.2、字符串常用函数
c函数速查与mac chm文档阅读器
4.2.1、strcpy拷贝
char *a = "china";
char dest[50];
strcpy(dest, a);
4.2.2、strcat拼接
strcat(dest, b);
printf("%s\n", dest);
4.2.3、匹配查找
strchr查单个字符
在一个串中查找给定字符的第一个匹配之处
只能查找一个字符
char* str = "I want go to USA!";
char *p = strchr(str, 'w');
if (p) {
printf("索引位置:%d\n", p - str);
}
strstr查多个字符
可以查多个字符
char *haystack = "I want go to USA!";
char *needle = "to";
char *p = strstr(haystack, needle);
if (p) {
printf("索引位置:%d\n", p - haystack);
}
五、结构体
数据类型:结构体是一种构造数据类型,把不同的数据类型整合起来成为一个自定义的数据类型,类似java中的bean。
操作方式:结构体是栈内存,不需要malloc,也不需要手动释放内存
struct Man
{
char name[20];
int age;
};
5.1、初始化结构体
type1:构建时便赋值
struct Man m1 = {"Jack", 21};
printf("%s,%d\n",m1.name,m1.age);
type2:初始化对象的字面量
注意:这里对于name的赋值不能写作m1.name = “Jack”;指针字符串修改会报错
struct Man m1;
strcpy(m1.name, "rose");
m1.age = 23;
printf("%s,%d\n",m1.name,m1.age);
type3:从另外一个结构体赋值
结构体是栈内存,这里m1与m2相互隔离。
struct Man m2 = m1;
m1.age = 25;
printf("%s,%d\n",m2.name,m2.age);
5.2、结构体的几种写法
type1:具名结构体
struct Man{
char name[20];
int age;
} m1,m2={"张三",19};
void main(){
strcpy(m1.name, "李四");
m1.age=18;
printf("%s,%d\n",m1.name,m1.age);
printf("%s,%d\n",m2.name,m2.age);
}
type2:匿名结构体
控制结构体变量的个数(限量版),相当于单例
struct
{
char name[20];
int age;
}m1;
type3:结构体嵌套
struct Teacher{
char name[20];
};
struct Student{
char name[20];
int age;
struct Teacher teacher;
};
void main(){
struct Student student1;
strcpy(student1.name,"张三");
student1.age=19;
strcpy(student1.teacher.name,"李老师");
printf("Student %s,%d的老师是%s\n",student1.name,student1.age,student1.teacher.name);
}
type4:结构体嵌套匿名结构体
struct Student{
char name[20];
int age;
struct Teacher{
char name[20];
} teacher;
};
5.3、指针与结构体数组
struct Man
{
char name[20];
int age;
};
5.3.1、结构体数组的遍历
struct Man
{
char name[20];
int age;
};
void main()
{
struct Man mans[] = { {"Jack", 20}, {"Rose",19} };
struct Man* p = mans;
for (; p < mans + 2; p++)
{
printf("%s, %d\n", p->name, p->age);
}
}
5.3.2、计算结构体数组长度
sizeof(mans) / sizeof(struct Man)
struct Man mans[] = { {"Jack", 20}, {"Rose",19} };
for (int i = 0; i < sizeof(mans) / sizeof(struct Man); i++)
{
printf("%s, %d\n", mans[i].name, mans[i].age);
}
5.4、结构体的大小(字节对齐)
结构体大小讲究《字节对齐》:结构体变量的大小,必须是最宽基本数据类型的整数倍。
作用:可以提升读取的效率
基本情况
4+8+补上的整数=16
struct Man
{
int age;
double weight;
};
void main()
{
struct Man m1 = {20, 89.0};
printf("%#x, %d\n", &m1, sizeof(m1));
}
4+4+4+4+4=20,不需要对齐
struct Man
{
int a;
struct Woman{
int b;
int c;
int d;
int e;
} f;
};
烧脑情况
4+4+8=16,不需要对齐
struct Man
{
int age;
int height;
double weight;
};
4+8:第一次对齐,需要补上4
4+8+4:第二次对齐,再补上一4
最后为24
struct Man
{
int age;
double weight;
int height;
};
5.5、结构体与动态内存分配
p->name="Jack";
(*p).name="Jack";
struct Man
{
char *name;
int age;
};
void main()
{
struct Man* m_p = (struct Man*)malloc(sizeof(struct Man) * 10);
struct Man* p = m_p;
p->name = "Jack";
(*p).name="afd";
p->age = 20;
p++;
p->name = "Rose";
p->age = 20;
struct Man* loop_p = m_p;
for (; loop_p < m_p + 2; loop_p++)
{
printf("%s,%d\n", loop_p->name, loop_p->age);
}
free(m_p);
}
六、typedef 类型取别名
6.1、基本数据类型取别名
typedef int Age;
typedef int* Ap;
6.2、结构体取别名
struct Man
{
char* name;
int age;
};
typedef struct Man JavaMan;
typedef struct Man* JM;
简写
typedef struct Woman
{
char* name;
int age;
} W, *WP;
测试结构体别名、结构体指针别名
void main()
{
JavaMan m1 = {"Jack",20};
JM p = &m1;
W w1 = { "Rose", 20 };
WP wp1 = &w1;
printf("%s,%d\n", w1.name, w1.age);
printf("%s,%d\n", wp1->name, wp1->age);
}
6.3、没有名称是否可以取别名
结构体可以
typedef struct {
char* name;
int age;
} Student;
void main(){
Student student;
student.name="zhangsan";
student.age=18;
printf("%s,%d\n",student.name,student.age);
}
6.4、结构体函数指针成员
Girl结构体类似于Java中的类,name和age类似于属性,sayHi类似于方法
struct Girl
{
char* name;
int age;
void(*sayHi)(char*);
};
void sayHi(char* text)
{
printf("%s\n",text);
}
void main()
{
struct Girl g1;
g1.name = "Lucy";
g1.age = 18;
g1.sayHi = sayHi;
g1.sayHi("hello");
}
使用typedef取别名
typedef struct Girl
{
char* name;
int age;
void(*sayHi)(char*);
} Girl;
typedef Girl* GirlP;
void sayHi(char* text)
{
printf("%s\n",text);
}
void main()
{
Girl g1 = {"Lucy", 18, sayHi};
GirlP gp1 = &g1;
gp1->sayHi("Byebye");
}
七、联合体
不同类型的变量共同占用一段内存(相互覆盖),联合变量任何时刻只有一个成员存在,节省内存
联合体变量的大小=最大的成员所占的字节数
union MyValue
{
int x;
int y;
double z;
};
void main(){
union MyValue d1;
d1.x=90;
d1.y=100;
d1.z=11.1;
printf("%d,%d,%lf\n",d1.x,d1.y,d1.z);
}
八、枚举
默认会赋值123。因此
enum Day
{
Monday = 0,
Tuesday = 1,
Wednesday = 2,
Thursday = 3,
Friday = 4,
Saturday = 5,
Sunday = 6
};
enum Day
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};
void main()
{
enum Day d = Saturday;
printf("%#x,%d\n",&d,d);
}
九、IO操作
9.1、读取文本文件
fopen:打开文件
fgetc:读文件(注意:windows中的fgetc与macos中的fgetc不一样)
char *path="/Users/zhanglei/Desktop/NewFile.txt";
FILE *fp=fopen(path, "r");
if (fp==NULL) {
printf("文件打开失败...");
return;
}
char ch;
while ((ch=fgetc(fp))!=EOF) {
printf("%c",ch);
}
fclose(fp);
9.2、写入文本文件
fputs:写入文件
char *path="/Users/zhanglei/Desktop/NewFile2.txt";
FILE *fp=fopen(path, "w");
if (fp==NULL) {
printf("文件打开失败...");
return;
}
char *text="hchmily@sina.com,程华才,学清路 8\n号科技财富中心 A";
fputs(text, fp);
fclose(fp);
9.3、文件复制
fread:读字节
fwrite:写字节
char *read_path="/Users/zhanglei/Desktop/NewFile.txt";
char *write_path="/Users/zhanglei/Desktop/NewFile2.txt";
FILE *read_fp = fopen(read_path,"rb");
FILE* write_fp = fopen(write_path, "wb");
char buff[50];
int len=0;
while ((len=fread(buff, sizeof(char), 50, read_fp))!=0) {
fwrite(buff, sizeof(char), len, write_fp);
}
fclose(read_fp);
fclose(write_fp);
9.4、获取文件的大小
fseek(fp, 0, SEEK_END):移动指针到文件末尾
ftell(fp):获取指针偏移量,即文件大小
char *read_path="/Users/zhanglei/Desktop/NewFile.txt";
FILE* fp=fopen(read_path, "r");
fseek(fp, 0, SEEK_END);
long fileSize=ftell(fp);
printf("%d\n",fileSize);
9.5、文本文件加解密
异或运算(可逆)
规则:1^1=0, 0^0=0, 1^0=1, 0^1=1 同为0,不同为1
fputc写入,这里不可写错成fputs
void cryptFun(char norma_path[],char crypt_path[]){
FILE* normal_fp=fopen(norma_path, "r");
FILE* crypt_fp=fopen(crypt_path, "w");
int ch;
while ((ch=fgetc(normal_fp))!=EOF) {
fputc(ch^9, crypt_fp);
}
fclose(normal_fp);
fclose(crypt_fp);
}
void main(){
char *norma_path="/Users/zhanglei/Desktop/NewFile.txt";
char *crypt_path="/Users/zhanglei/Desktop/NewFile_crypt.txt";
char *decrypt_path="/Users/zhanglei/Desktop/NewFile_decrypt.txt";
cryptFun(norma_path,crypt_path);
cryptFun(crypt_path,decrypt_path);
}
9.6、进制文件加解密
读取二进制文件中的数据时,一个一个字符读取
strlen:测量字符串长度,需要导#include <string.h>库
rb:读字节
wb:写字节
void cryptFun(char norma_path[],char crypt_path[], char password[]){
FILE* normal_fp=fopen(norma_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) {
fputc(ch^password[i++ % pwd_len], crypt_fp);
}
fclose(normal_fp);
fclose(crypt_fp);
}
void main(){
char *norma_path="/Users/zhanglei/Desktop/liuyan.png";
char *crypt_path="/Users/zhanglei/Desktop/liuyan_crypt.png";
char *decrypt_path="/Users/zhanglei/Desktop/liuyan_decrypt.png";
cryptFun(norma_path,crypt_path,"iloveyou");
cryptFun(crypt_path,decrypt_path,"iloveyou");
}
十、预编译
C语言执行的流程
1、预编译(预处理):为编译做准备工作,完成代码文本的替换工作(宏定义、宏替换、预编译指令)
2、编译:形成目标代码(.obj)
3、连接:将目标代码与C函数库连接合并,形成最终的可执行文件
4、执行
10.1、define指令作用
定义标示
#ifdef __cplusplus
定义常数(便于修改与阅读)
#define MAX 100
void main(){
printf("%d\n", MAX);
}
定义“宏函数”
可以动态替换函数名称
void dn_com_jni_read() {
printf("read\n");
}
void dn_com_jni_write() {
printf("write\n");
}
#define jni(NAME) dn_com_jni_##NAME();
void main()
{
jni(write);
}
10.2、日志输出示例
定义LOG、LOG_I、LOG_E三个宏函数
FORMAT
...
__VA_ARGS__
#define LOG(FORMAT,...) printf(FORMAT, __VA_ARGS__);
#define LOG_I(FORMAT,...) printf("INFO:"); printf(FORMAT, __VA_ARGS__);
#define LOG_E(FORMAT,...) printf("ERRO:"); printf(FORMAT, __VA_ARGS__);
void main()
{
LOG("%s,%d\n","Jason",18);
LOG_I("%s\n","message");
LOG_E("%s\n", "fire");
}
升级版本
宏函数LOG_I、LOG_E复用了LOG宏函数
#define LOG(LEVEL, FORMAT, ...) printf(LEVEL); printf(FORMAT, __VA_ARGS__);
#define LOG_I(FORMAT, ...) LOG("INFO:", FORMAT, __VA_ARGS__)
#define LOG_E(FORMAT, ...) LOG("ERRO:", FORMAT, __VA_ARGS__)
void main()
{
LOG("错误级别:","%s,%d\n","Jason",18);
LOG_I("%s\n","message");
LOG_E("%s\n", "fire");
}
10.3、定义标示,解决重复引用
通过#define AH来解决重复循环include的问题(在c语言中循环引入会编译报错)。
#ifndef AH
#define AH
#include "B.h"
#endif
或者简洁语法
#pragma once
代码示例。MyCLanguage.c部分
#include "A.h"
void printfA()
{
printf("print A");
}
void printfB()
{
printf("print B");
}
void main()
{
printfA();
}
A.h部分
#pragma once
#include "B.h"
void printfA();
B.h部分
#pragma once
#include "A.h"
void printfB();
|