8 minutes
C语言核心内容 笔记
C语言核心
函数
基本函数
#include <stdio.h>
// 函数的声明
void Hello();
int main()
{
// 最简单函数的调用
Hello();
return 0;
}
void Hello()
{
printf("Hello!\n");
}
#include <stdio.h>
void Hello()
{
printf("Hello!\n");
}
int main()
{
// 最简单函数的调用
Hello();
return 0;
}
函数的定义
返回值类型 函数名(参数列表)
{
函数体
}
函数声明
所谓声明就是(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上
参数
形参 形式参数
实参 实际参数
传值和传址
传递数值 会受到作用域的限制
传递地址 任意门不会受到作用域的限制
可变参数
#include <stdarg.h>
- va_list
- va_start
- va_arg
- va_end
#include <stdio.h>
#include <stdarg.h>
int sum(int n, ...);
int sum(int n, ...)
{
int i, sum = 0;
va_list vap;
va_start(vap, n);
for(i = 0;i<n;i++)
{
sum += va_arg(vap,int);
}
va_end(vap);
return sum;
}
int main()
{
int result;
result = sum(3, 1, 2, 3);
printf("result = %d\n", result);
}
带参数的函数
#include <stdio.h>
void add(int a, int b);
int main()
{
add(5, 6);
return 0;
}
// 参数里的a和b都是形式参数(形参),我们在调用的时候,需要填写实际参数(实参)
void add(int a, int b){
int c = a + b;
printf("%d", c);
}
函数的返回值
#include <stdio.h>
int add(int a, int b);
int main()
{
int nNum = add(5, 6);
printf("%d", nNum);
return 0;
}
int add(int a, int b){
int c = a + b;
return c;
}
递归
#include <stdio.h>
void hello(int n);
int main()
{
hello(1);
return 0;
}
void hello(int n)
{
if(n <= 10)
{
printf("hello!\n");
hello(n+1);
}
else
{
printf("end");
}
}
数组
定义
-
类型 数组名 [元素个数]
int a[6]; char b[24]; double c[3];
-
数组不能动态定义
访问
-
数组名[下标]
a[0];//访问a数组中的第一个元素 b[1];//访问b数组中的第二个元素 c[5];//访问c数组中的第六个元素
-
注意
int a[5]; // 创建一个具有五个的数组 a[0];// 访问第一个元素的下标是0,不是1 a[5];// 报错,因为第五个元素的下标是a[4]
循环跟数组的关系
我们常常需要使用循环来访问数组
int a[10]; for (i = 0;i < 10;i++) { a[i] = i; }
初始化
-
将数组中所用元素初始化为0,可以这么写:
int a[10] = {0};// 事实上这里只是将第一个元素赋值为0
-
如果是赋予不同的值,那么用逗号分隔开即可:
int a[10] = {1,2,3,4,5,6,7,8,9,0};
-
你还可以只给一部分元素赋值,未被赋值的元素自动初始化为0:
int a[10] = {1,2,3,4,5,6}; // 表示为前边6个元素赋值,后边4个元素系统自动初始化为0
-
有时候还可以偷懒,可以只给出各个元素的值,而不指定数组的长度(因为编译器会根据值的个数自动判断数组的长度):
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
-
C99增加了一种新特性:指定初始化的元素。这样就可以只对数组中的某些指定元素进行初始化赋值,而来被赋值的元素自动初始化为0;
int a[10] = {[3] = 3, [5] = 5, [8] = 8};
最新的标准:会出现数组动态定义和越界问题的解决情况
字符数组
#include
int main()
{
// 初始化字符数组的每个元素
char str1[10] = {'F','i','s','h','C','\0'};
// 可以不写元素的个数,因为编译器会自动计算
char str3[] = {'F','i','s','h','C','\0'};
// 使用字符串常量初始化字符数组
char str4[] = {"FishC"};
// 使用字符串常量初始化,可以省略大括号
char str5[] = "FishC" ;
// 初始化字符串
char str6[128] = "FishC";
}
字符串处理函数
int main()
{
// strlen 字符串长度
char str[] = "I love C language";
printf("%u",str)
// strcpy 和 strncpy 复制和拷贝字符串
char str1[] = "Original String";
char str2[] = "New String";
strcpy(str1,str2);//大坑copy小坑会出现溢出
strncpy(str1,str2,5);
// strcat和strncat 连接字符串
strcat(str1,str2);
strncat(str1,str2,5);
//strcmp和strncmp 比较字符串
if (!strcmp(str1,str2))
{
printf("两个字符串完全一致\n");
}
else
{
printf("两个字符串存在差异!\n")
}
return 0
}
二维数组
平面、矩阵
定义
-
类型 数组名 [常用表达式][常量表达式]
int a[6][6]; // 6*6, 6行6列 char b[4][5]; // 4*5, 4行5列 double c[6][3]; // 6*3, 6行3列
存放方式依然是线性
访问
-
1数组名[下标][下标]
a[0][0]; // 访问a数组中第1行第1列的元素 b[1][3]; // 访问b数组中第2行第4列的元素 c[3][3]; // 访问c数组中第4行第4列的元素
-
同样需要注意下标的取值范围,以防数组的越界访问
二维数组初始化
-
由于二维数组在内存中是线性存放的,因此可以将所有的数据写在一个花括号内:
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
-
为了更直观地表示元素的分布,可以用大括号将每一行的元素括起来:
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
或者
int a[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
-
二维数组也可以仅对部分元素赋初值:
int a[3][4] = {{1},{5},{9}};
-
如果希望整个二维数组初始化为0,那么直接在大括号里写一个0即可:
int a[3][4] = {0};
-
C99同样增加了一种新特性:指定初始化的元素。这样就可以只对数组中的某些指定元素进行初始化赋值,而未被赋值的元素自动初始化为0:
int a[3][4] = {[0][0] = 1, [1][1] = 2, [2][2] = 3};
-
二维数组的初始化也能偷懒,让编译器根据元素的数量计算数组的长度。但只有第1维的元素个数可以不写,其他维度必须写上:
int a[][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
矩阵的转置
遍历数字变更
进行转置
字符串
#include <stdio.h>
int main()
{
// 这种声明方式会默认在最后添加一个\0
// 在我们这个C和C++中,字符串都是以00,也就是、0结尾的
char szStr[] = { "My name is rkvir" };
// 像下面这种,没有默认添加的,就需要字节手动添加00,也就是\0
char cStr[] = {'M','y',' ','n','a','m','e', ' ', 'i','s',' ','r','k','v','i','r','\0'};
char * pStr = "My name is rkvir";
printf("%s",cStr);
}
字符操作
获取一个字符并进行输出:
char a;
a = getchar();
putchar();
C语言ctype.h
#include <ctype.h>
isalpha();
isalnum();
isblank();
iscntrl();
isdigit();
isgraph();
islower();
isprint();
ispunct();
isspace();
isupper();
isxdigit();
大小写处理函数
char a = 'a';
char flag = toupper(a);
char flags = tolower(flag);
字符串接收输出
#include <stdio.h>
int main()
{
char string;
while ((string = getchar()) != EOF)
{
putchar(string);
}
return 0;
}
// EOF
// CTRL+C
// CTRL+Z
字符串输入
// gets fgets scanf
char szStr[50]
gets(szStr); // 以回车结尾
fget(szStr,50,stdin); // 接受指定长度,也会接受回车
scanf("%s",&szStr); // 接受地址
字符串输出
char *pStr = "Hello World!\n";
// puts printf
// puts这个函数会自动添加换行符
puts(szStr);
fputs(szStr, stdout);
printf("%s",szStr);
字符串操作
字符串长度
#include <string.h>
int main()
{
// strlen
char szStr[] = "Hello World!";
int Length = strlen(szStr);
return 0;
}
字符串拼接
#include <string.h>
int main()
{
// strcat strncat
char szStr1[] = "Hello";
char szStr2[] = "rkvir";
/*
*strcat有两个参数
*第一个参数是目的字符串,第二个参数是源字符串
*strcat的作用就是把源字符串拼接大屏目的字符串的末尾处,并且将拼接后的字符串存储到目的字符串中
*/
strcat(); // 会出溢出
// strncat 控制拼接长度
strncat();
return 0;
}
字符串比对
#include <string.h>
int main()
{
char szStr1[] = "Hello World!";
char szStr2[] = "Hello";
char szStr3[] = "Hello World!";
// strcmp strncmp
/* strcmp有两个参数,就是待比较的两个字符串
* 返回值是结果
* 如果返回值等于0,那么两个字符串就相等
* 如果返回值不等于0,那么两个字符串就不相等
*/
int ret = strcmp(szStr1, szStr3);
/* strncmp有三个参数
* 前两个参数是待比较的两个字符串
* 但是第三个字符,是比对的字符数
*/
int ret0 = strncmp(szStr1, szStr2, 6);
return 0;
}
字符串拷贝
#include <string.h>
int main()
{
char szStr1[50] = {0};
char szStr2[] = "Hello";
// strcpy strncpy
/* strcpy有两个参数
* 第一个参数是目的字符串
* 第二个参数是源字符串
* strcpy的作用就是把源字符串拷贝到目的字符串
*/
strcpy(szStr1,szStr2);
strncpy(szStr1,szStr2.2);
return 0;
}
字符串格式化
#include <stdio.h>
#include <string.h>
int main()
{
char szStr1[] = "Hello";
int nNum = 11;
char cS = 'A';
char szStr2[100] = {0};
/* sprintf 三个参数
* 第一个参数,就是目的字符串
* 第二个参数,是格式化符号
* 第三个参数就是变量
*/
sprintf(szStr2, "%s %d %c", szStr1,nNum,cs);
}
指针
内存储存的真相:数据储存在内存地址上
指针和指针变量
指针:指向内存储存的地址
指针变量
定义指针变量
-
类型名 *指针变量名
char *pa;// 定义一个指向字符型的指针变量 int *pb; // 定义一个指向整型的指针变量
取地址运算符和取值运算符
-
如果需要获取某个变量的地址,可以使用取地址运算符(&):
char *pa = &a; int *pb = &f;
-
如果需要访问指针变量指向的数据,可以使用取值运算符(*):
printf("%c,%d\n",*pa,*pd);
避免访问未初始化的指针
#include <studio.h>
int main()
{
int *a;
*a = 123;
return 0;
}
操作非常危险
指针和数组
数组名的真实身份
数组名其实是数组第一个元素的地址!
指向数组的指针
-
如果用一个指针指向数组,应该怎么做呢?
char *p; p = a; // 语句1 p = &a[0]; // 语句2
==指针的运算==
- 当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素
- 对比标准的下标法访问数组元素,这种使用指针进行间接访问的方法叫做指针法
- 需要郑重强调的是:p+1并不是简单地将地址加1,而是指向数组的下一个元素
指针和数组的区别
数组名只是一个地址,而指针是一个左值
指针数组
int *p1[5]; //指针数组
// 初始化
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int *pl[5] = {&a ,&b ,&c ,&d ,&e };
int i;
for (i = 0;i < 5; i++)
{
printf("%d\n",*pl[i]);
}
return 0;
}
指针数组是一个数组,每个数组元素存放一个指针变量
数组指针
int (*p2)[5];
数组指针是一个指针,它指向的是一个数组
指针和二维数组
array表示的是什么?
array表示指向5个元素的数组指针(即指向一维数组的数组指针)
*(array+i) == array[i];
*(*(array+i)+j) == array[i][j];
*(*(*(array+i)+j)+k) == array[i][j][k];
数组指针和二维指针
-
初始化二维数组是可以偷懒的:
int array[2][3] = {{0,1,2}, {3,4,5}};
可以写成
int array[][3] = {{0,1,2},{3,4,5}};
-
定义一个数组指针是这样的:
int (*p)[3];
void指针
void指针我们把它称之为通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给void指针
取出void指针的数值需要使用到强制类型转换
NULL指针
NULL指针即为空指针
#define NULL ((void *)0)
当你还不清楚要将指针初始化为什么地址时,请将它初始化NULL;在对指针进行解引用时,先检查该指针是否为NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。
指针之指针
指向指针的指针
#include <stdio.h>
int main()
{
int num = 520;
int *p = #
int **pp = &p;
return 0;
}
指针数组和指向指针的指针
至少有两个好处:
- 避免重复分配内存
- 只需要进行一处修改
代码的灵活性和安全性都有了显著地提高!
数组指针和二维数组
常量和指针
const关键字,使变量可读取而不可修改
const int price = 520;
const char a = 'a';
const float pi = 3.14
- 指针可以修改为指向不同的常量
- 指针可以修改为指向不同的变量
- 可以通过解引用来读取指针指向的数据
- 不可以通过解引用修改指针指向的数据
常量指针
int num = 520;
int * const p = #
- 指向非常量的常量指针
- 指针自身不可以被修改
- 指针指向的值可以被修改
- 指向常量的常量指针
- 指针自身不可以被修改
- 指针指向的值也不可以被修改
指针与函数
指针函数,是函数
函数指针,是指针
函数指针
int add(int a, int b)
{
return a + b;
}
typedef int(*Myadd)(int a, int b);
Myadd my = add;
int c = my(1, 2);
应用场景:
- 系统库API
- load dll
- 模块基质
- GetProcAddress 获取函数地址
#include <stdio.h>
int add(int a, int b);
int main()
{
int (*Myadd)(int a, int b);
//不带括号不带参数的函数名,其实就是函数的首地址
MyAdd = add;
return 0;
}
int add(int a, int b)
{
return a+b;
}
指针函数
#include <stdio.h>
int *add(int *a);
int main()
{
int arrNum[5] = {1,2,3,4,5};
int *ret = add(arrNum);
int nNum = ret[2]
return 0;
}
int *add(int *a)
{
return a;
}
存储类
- 静态存储时期
- 自动存储时期
静态存储类
- 自动
- 寄存器 register
- 具有外部链接的静态存储类 extern
- 具有内部链接的静态存储类 static
- 空链接的静态存储类 static
动态内存管理
申请内存
释放内存
数组是默认给你开辟了一块连续空间
malloc
在堆上面申请内存,返回的是void *
单位是字节
memset
,把一段内存,全部刷成你想要的值
free
,释放一段内存的空间
#include <stdlib.h>
int main()
{
char * szStr;
szStr = (char *)malloc(200 * sizeof(char));
memset(szStr, 0, 200 * sizeof(char));
free(szStr);
return 0;
}
文件操作
文件指针:位于文件头部的指针,然后可以对其移动,这样就可以读取文件任意位置的数据了,文件指针移动到末尾出的时候,文件就读取完了
fopen的模式
模式 | 描述 |
---|---|
“r” | 打开一个用于读取的文件,该文件必须存在。 |
“w” | 创建一个用于写入的空文件,如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件视为一个新的空文件。 |
“a” | 追加到一个文件,写操作向文件末尾追加数据,如果文件不存在,则创建文件。 |
“r+” | 打开一个用于更新的文件,可读取也可写入,该文件必须存在。 |
“w+” | 创建一个用于读写的空文件。 |
“a+” | 打开一个用于读取和追加的文件。 |
#include <stdio.h>
int main()
{
// 声明文件指针
FILE * pFile;
// 存储读取到的文件的内存空间,也就是一个char * 类型的字符串
char * szReadTextBuffer;
// 读取到的文件的尺寸
int nReadFileSize;
// 获取读取到的文件缓冲区字节数
int nReadRetSize = 0;
/* fopen,fopen有两个参数
* 第一个参数是需要打开的文件的路径
* 第二个参数是打开的模式
*/
if((pFile = fopen("file.txt","rb")) == NULL)
{
// 处理一下如果打开文件失败了
printf("Open File Failed!\n");
exit(0);
}
// 因为设置文件指针到末尾处了,我们可以通过文件指针位置的方式,获取文件大小
//设置文件指针到末尾处
fseek(pFile, 0, SEEK_EMD);
// 获取文件指针的位置,获取文件大小
nReadFileSize = ftell(pFile);
//文件指针复位到初始位置
rewind(pFile);
// 根据文件尺寸申请一块足够大小的内存空间
szReadTextBuffer = (char *)malloc(sizeof(char)*nReadFileSize +1);
if(szReadTextBuffer == NULL)
{
// 处理内存申请失败代码
printf("malloc memory failed!\n");
exit(0)
}
// 对申请到的内存进行初始化
menset(szReadTextBuffer, 0, nReadFileSize + 1);
// 将已经获取到文件指针的文件的内容读取到已经申请好的缓冲区中,并且返回真实读取到的内容长度
nReadRetSize = fread(szReadTextBuffer, 1, nReadFileSize, pFile);
// 判断是否读取失败,真实读取到的内存长度
if(nReadFileSize != nReadRetSize)
{
// 处理读取失败的代码
printf("Read File Failed!\n");
exit(0);
}
fclose(pFile);
return 0;
}
读取文件
int main()
{
FILE* pFile;
char* szReadTextBuffer;
int nReadFileSize;
int nReadRetSize;
pFile = fopen("C:\\Users\\15890\\Desktop\\file.txt","rb");
if (pFile == NULL)
{
printf("open file failed!\n");
exit(0);
}
fseek(pFile, 0, SEEK_END);
nReadFileSize = ftell(pFile);
rewind(pFile);
szReadTextBuffer = (char*)malloc((nReadFileSize * sizeof(char)) + 1);
if (szReadTextBuffer == NULL)
{
printf("mallco memory failed!\n");
exit(0);
}
memset(szReadTextBuffer, 0, nReadFileSize + 1);
nReadRetSize = fread(szReadTextBuffer, 1, nReadFileSize, pFile);
if (nReadFileSize != nReadRetSize)
{
printf("Read file failed!\n");
exit(0);
}
puts(szReadTextBuffer);
fclose(pFile);
return 0;
}
写入文件
int main()
{
char* szWriteBuffer = "I love the reverse program!\n";
int nWriteSize = 0;
FILE* pFile;
pFile = fopen("C:\\Users\\15890\\Desktop\\myfile.txt", "wb");
if (pFile == 0)
{
printf("failed!\n");
exit(0);
}
fwrite(szWriteBuffer, strlen(szWriteBuffer), 1, pFile);
fclose(pFile);
return 0;
}
结构体、联合体、枚举
结构体
int main()
{
// 用struct标记为结构体,然后后面是结构体的名字
struct NPC
{
// 名字
char * Name;
// 血量
int HP;
// 魔法值
int MP;
// 坐标
int x;
int y;
int z;
}
// 给结构体小儿的各种变量赋值
NPC xiaoer;
xiaoer.Name = "wangxiaoer";
xiaoer.HP = 100;
xiaoer.MP = 0;
xiaoer.x = 111;
xiaoer.y = 222;
xiaoer.z = 0;
}
结构体指针
int main()
{
// 用struct标记为结构体,然后后面是结构体的名字
struct NPC
{
// 名字
char * Name;
// 血量
int HP;
// 魔法值
int MP;
// 坐标
int x;
int y;
int z;
}
struct NPC npcarry[100];
struct NPC *npcindex;
npcindex = &npcarry[0];
npcindex -> Name = "xiaoyi";
// *npcindex.Name = "xiaoyi";
npcindex++;
npcindex -> Name = "xiaoer";
// *npcindex.Name = "xiaoer";
return 0;
}
结构体参数
位域
struct {
int a : 8;
int b : 8;
int c : 8;
int d : 8;
}rk
联合体
union Info
{
char PlayerName[20];
int MP;
float x;
}
union Info MyInfo;
strcpy(MyInfo.PlayerName, "NPC");
printf("%s\n%d\n%f\n", MyInfo.PlayerName, MyInfo.MP, MyInfo.x);
联合体所有成员共用一个地址,只能使用一个成员
枚举
int main()
{
enum color
{
red;
green;
blue;
}
int flag = 0;
scanf("%d", &flag);
switch(flag)
{
case red:
printf("red\n");
break;
case green:
printf("green\n");
break;
case blue:
printf("blue\n");
break;
default:
break;
}
return 0;
}
高级数据表示
顺序结构
数组
栈
队列
链式结构
链表
链栈
链队列
树
二叉树
- 度不能超过2
- 有序树
- 无序树
遍历
- 先序遍历:根 左 右
- 中序遍历:左 根 右
- 后序遍历:左 右 根