计算机相关
系统组成
- 硬件系统
- 主机
- 中央处理器(CPU)
- 寄存器
- 运算器
- 控制器
- 内存储器
- 随机存储器(RAM)
- 只读存储器(ROM)
- 外部设备(外设)
- 输入设备:鼠标、键盘、摄像头等
- 输出设备:声卡、显卡等
- 外存储器:光盘、硬盘、U盘等
- 软件系统
- 系统软件
- 操作系统
- 语言处理系统
- 数据库管理系统
- 系统服务程序
- 应用软件
内存储器与外存储器
采用电信号存储数据,速度快,但是断点数据丢失
如光盘,采用磁信号存储数据,速度慢,但是可以用来做数据持久化
数据计算时的存储变化
如一个磁盘中的数据: 磁盘 > 磁盘缓存 > 内存 > CPU缓存 > 寄存器 > 运算
寄存器
几个概念
- 寄存器是CPU内部的最基本的存储单元
- CPU通过总线(数据、地址、控制)来和外部设备打交道
- 如果CPU的总线是8位,寄存器也是8位,则这就是个8位的CPU;倘若总线16位,寄存器32位,那么则是一个准32位CPU
- 在64位的CPU上运行64位的操作系统,则这是个64位的操作系统,若运行的是32位的操作系统,则这是个32位的操作系统
- 64位的软件不能运行在32位的CPU上
寄存器名称
8位CPU | 16位CPU | 32位CPU | 64位CPU |
---|
A | AX | EAX | RAX | B | BX | EBX | RBX | C | CX | ECX | RCX | D | DX | EDX | RDX |
对寄存器的操作
C语言对寄存器做了封装,只能做些简单的操作,如果想进行更深层次的操作,需要使用汇编语言,对此,可以在C语言中嵌套编写汇编语言
#include <stdio.h>
int main(void)
{
int a, b, c;
__asm
{
mov a, 3
mov b, 4
mov eax, a
add eax, b
mov c, eax
}
printf("c: %d\n", c);
system("pause");
return 0;
}
Visual Studio相关
VS执行程序时窗口闪退问题的解决方案
- 通过
system 函数
#include <stdio.h>
int main(void)
{
printf("hello world\n");
system("pause");
return 0;
}
- 通过修改VS配置
右键项目 > 属性 > 配置属性 > 链接器 > 系统 > 子系统:控制台
代码片段管理
- 新建一个,如main函数的
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2019/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>#1</Title>
<Shortcut>#1</Shortcut>
<Description>c语言main函数</Description>
<Author>Microsoft Corporation</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>expression</ID>
<ToolTip>要计算的表达式</ToolTip>
<Default>true</Default>
</Literal>
</Declarations>
<Code Language="cpp"><![CDATA[#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
int main(void)
{
$selected$$end$
system("pause");
return EXIT_SUCCESS;
}
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2019/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>#2</Title>
<Shortcut>#2</Shortcut>
<Description>c++语言main函数</Description>
<Author>Microsoft Corporation</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>expression</ID>
<ToolTip>要计算的表达式</ToolTip>
<Default>true</Default>
</Literal>
</Declarations>
<Code Language="cpp"><![CDATA[#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int main()
{
$selected$$end$
system("pause");
return EXIT_SUCCESS;
}]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
- 导入
工具 -> 代码片段管理 -> Visual C++
几个常用快捷键
- 格式化代码
ctrl k + ctrl f
- 注释
ctrl k + ctrl c
- 去除注释
ctrl k + ctrl u
- 只编译不运行
ctrl shift b
GCC编译的四个步骤
记事本创建hello.c
#include <stdio.h>
#define PI 3.14159
int main(void)
{
int a = 1;
#ifdef PI
printf("PI: %f\n", PI);
#endif
printf("hello c\n");
return 0;
}
步骤一:预处理
- 参数:-E
- 命令:
gcc -E hello.c -o hello.i - -o 选项:表示重命名
- 主要工作
- 展开头文件
即,会将所有引入的头文件,如此例中的<stdio.h>中的stdio.h文件进行展开,需要注意的是,只是展开但是不会对头文件进行检查和校验,即写成<test.txt>也一样会将text.txt进行展开。
gcc -E hello.c -o hello.i -I . //需要使用 -I 选项指定test.txt的所在路径
- 展开条件编译
即,在此步骤中,条件编译#ifdef部分代码会被展开执行
#ifdef PI
printf("PI: %f\n", PI);
#endif
执行后的 hello.i
printf("PI: %f\n", PI);
- 替换注释
即,在此步骤中,无论单行注释还是多行注释,都会被替换为空白行
- 替换宏定义
即,在此步骤中,宏定义会执行替换工作,如例中的宏变量PI,会被替换为其宏值3.14159
printf("PI: %f\n", 3.14159);
步骤二:编译
- 参数:-S
- 命令:
gcc -S hello.i -o hello.s - 主要工作
- 会逐行检查语法,也因此编译过程是最为耗时的过程
- 将C语言转换成汇编指令
步骤三:汇编
- 参数:-c
- 命令:
gcc -c hello.s -o hello.o - 主要工作:将汇编指令翻译为机器码(二进制编码)
步骤四:链接
- 参数:无
- 命令:
gcc hello.o -o hello.exe //windows系统
gcc hello.o -o hello //linux系统,之后 ./hello 即可调用执行
- 主要工作
- 数据段合并
- 数据地址回填
- 库引入
即除了头文件外,还需要引入许多其他系统库,可以使用depends.exe查看所引入的库(将hello.exe拖入其中即可)
常量与变量
常量的定义
#define PI 3.1415926
const int a = 1;
变量的定义
int var = 1;
变量的声明
int var;
extern int a;
#include <stdio.h>
int a;
int main(void)
{
printf("a: %d\n", a);
return 0;
}
#include <stdio.h>
int main(void)
{
int a;
printf("a: %d\n", a);
return 0;
}
#include <stdio.h>
extern int a;
int main(void)
{
printf("a: %d\n", a);
return 0;
}
数据类型
整形
几种整形
- short:2字节
- int:4字节
- long:windows下4字节;linux下32位系统为4字节,64位为8字节
- long long:8字节
有符号与无符号
- 关键字
- signed:有符号
默认就是有符号,所以通常省略不写
- unsigned:无符号
即存储的字节中无符号位
- 字节数
有符号和无符号的字节数一样
其他几个常用类型
字符类型
- 关键字:char
- 字节数:1
- 常见字符与其对应的数值
- char的取值范围
- 规定
00000000: 0; 10000000: -128
- 有符号
-2^(8-1) ~ 2^(8-1) - 1; // -128 ~ 127 减1是因为从0开始
- 无符号
0 ~ -2^(8) - 1; // 0 ~ 255 减1是因为从0开始
浮点类型
- 字节数
- float:4
- double:8
- 注意
float f = 1.1f; //需要加上f后缀,否则默认视为double类型
字符串类型
在C语言中,字符串类型有个很重要的特点,就是\0,这是字符串的结束标记,且对于用户设置的字符串,系统会自动添加该字符,如定义的 “abc”,实质上是4个字符,即"abc\0"
printf("ab\0c");
char str[2] = { 'a', 'b' };
printf("str: %s\d", str);
字符串相当于是字符数组的一个特殊形式,即必须以’\0’结尾
int str[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
int str[] = "hello";
char str[6] = {0};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
char str1[6] = { 0 };
scanf("%s", str1);
printf("res1: %s\n", str1);
char str2[6] = { 0 };
for (size_t i = 0; i < sizeof(str1) / sizeof(str1[0]); i++)
{
scanf("%c", &str2[i]);
}
printf("res2: %s\n", str1);
return 0;
}
int main(void)
{
char str1[10];
gets_s(str1, 11);
printf("str1 = %s\n", str1);
char str2[11];
printf("str2 = %s\n", fgets(str2, sizeof(str2), stdin));
return 0;
}
int main(void)
{
char str[] = "hello world";
int ret1 = puts(str);
printf("ret1 = %d\n", ret1);
int ret2 = fputs(str, stdout);
printf("ret2 = %d", ret2);
return 0;
}
int main(void)
{
char str1[] = "hello world";
printf("sizeof: %u\n", sizeof(str1));
printf("strlen: %u\n", strlen(str1));
char str2[] = "hello\0world";
printf("sizeof: %u\n", sizeof(str2));
printf("strlen: %u\n", strlen(str2));
return 0;
}
sizeof & printf
sizeof
- 不是个函数,无需引入头文件,作为一个关键字,用于计算一个数据类型的字节大小
- 返回值类型为
size_t ,通过typedef 对unsigned int 起的一个别名 - 如果传入的是变量值,则括号可以省略
printf
常见格式匹配符 | 说明 |
---|
%d | signed int 10进制 | %o | signed int 8进制 | %x | signed int 16进制 | %hd | signed short | %ld | signed long | %lld | signed long long | %u | unsigned int | %hu | unsigned short | %lu | unsigned long | %llu | unsigned long long | %c | char | %s | string | %f | float(默认保留6位小数) | %.nf | float(指定保留n位小数) | %m.nf | float( 整数+1(小数点)+小数n = m ,不足默认用空格左填充) | %0m.nf | float( 不足时用0左填充 ) | %lf | double | %% | % |
示例
#include <stdio.h>
extern int a;
int main(void)
{
short s = 2;
int i = 4;
long l = 8;
long long ll = 8;
unsigned short us = 2;
unsigned int ui = 4;
unsigned long ul = 8;
unsigned long long ull = 8;
printf("short: %u\n", sizeof s);
printf("int: %u\n", sizeof(int));
printf("long: %u\n", sizeof(long));
printf("long long: %u\n", sizeof(long long));
printf("unsigned short: %u\n", sizeof(unsigned short));
printf("unsigned int: %u\n", sizeof(unsigned int));
printf("unsigned long: %u\n", sizeof(unsigned long));
printf("unsigned long long: %u\n", sizeof(unsigned long long));
return 0;
}
类型限定符
extern
表示 变量/函数 声明,用该关键字声明后的 变量/函数 将不会自动创建内存存储空间
volatile
用于阻止编译器自行优化代码
volatile int flag = 0;
flag = 1;
flag = 0;
flag = 1;
flag = 0;
const
见常量的定义
register
定义一个寄存器变量,即用该关键字定义的变量,将直接存放到寄存器中,但要注意的是不一定保证每次都存放成功,如寄存器已满的情况下将存放失败
寄存器没有和内存一样的地址的概念
int a = 5; //默认将a放入内存中
a + 10; //当使用a时,会先将a放入寄存器中,然后开始执行相关运算
数值存储方式
存储方式
计算机内部以补码的方式存储一个数值
原码
- 最高位作为符号位,0正1负
- 其他部分为该数值本身绝对值的二进制表示
数值 | 原码 |
---|
15 | 00001111 | -15 | 10001111 | 0 | 00000000 | -0 | 10000000 |
反码
- 正数的反码同原码
- 负数的反码为原码符号位不变,其他位置取反
数值 | 反码 |
---|
15 | 00001111 | -15 | 11110000 | 0 | 00000000 | -0 | 11111111 |
补码
- 正数的补码同原码
- 负数的补码为反码符号位不变,加 1
- 符号位不变,补码 - 1,然后取反
- 符号位不变,取反,再加 1
数值 | 反码 |
---|
15 | 00001111 | -15 | 11110001 | 0 | 00000000 | -0 | 10000000 |
示例:43 - 27
+43的补码:00101011 -27的原码:10011011 -27的反码:11100100 -27的补码:11100101
00101011 11100101 —————— 00010000 = 16
数据溢出
符号位溢出
对于有符号的数值可能发生,发生后数值的正负符号将改变
char c = 127 + 1;
01111111
00000001
-------------
10000000(补码)
11111111(反码)
10000000(原码) = -128
最高位溢出
对于无符号的数值可能发生,由于最高位丢失,所以发生后数值的差异可能十分大
unsigned char c = 255 + 1;
11111111
00000001
--------------
100000000 -> 溢出后:00000000 = 0
运算符
算数运算符
同java
赋值运算符
同java
比较运算符
同java
逻辑运算符
同java
c中 0、’\0’ 也表示false
int aa = 0;
if (!aa)
{
printf("aa0 = %d", aa);
}
else
{
printf("aa1 = %d", aa);
}
三目运算符
同java
逗号运算符
int a = 10, b, c = 30;
int x = (a = 100, b = a * 2, c = c * 10 + a + b);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("x = %d\n", x);
return 0;
类型转换
隐式类型转换
注意:如 float、int 进行运算时,都转为链中的交点,即double
char, short
signed int
unsigned int
long
double
float
强制类型转换
同java
分支语句
同java
循环语句
同java
数组
- 相同数据类型的有序(地址)集合
- 数组名为第一个元素的地址
- 数组空间大小为所有元素大小之和
#include <stdio.h>
int main(void)
{
int arr[5] = { 4, 6, 1, 2, 90 };
printf("arr = %x\n", arr);
printf("&arr[0] = %x\n", &arr[0]);
printf("&arr[1] = %x\n", &arr[1]);
printf("&arr[2] = %p\n", &arr[2]);
printf("&arr[3] = %p\n", &arr[3]);
printf("&arr[4] = %p\n", &arr[4]);
printf("数组元素大小:%u\n", sizeof(int));
printf("数组大小:%u\n", sizeof(arr));
return 0;
}
- 初始化一个全为0的数组,需要设置至少一个0元素,否则将是随机数
int arr1[10];
int arr2[10] = {0};
int arr3[3][5] = {0};
int arr[10];
arr[0] = 1;
- 可以不指定个数,即数组可以自己计算元素个数
int arr[] = {1, 2, 3};
函数
函数声明
返回值类型 函数名(形参列表);
隐式函数声明
函数声明案例
- 函数调用前有函数定义:可以正常调用
#include <stdio.h>
void add(int a, int b)
{
printf("a + b = %d", a + b);
}
int main(void)
{
add(1, 2);
return 0;
}
- 函数调用前无函数定义:自动隐式声明
如果函数恰好返回值为int,则正常调用,否则将抛出重复声明定义的错误
- 函数调用前无函数定义,但是有函数声明:可以正常调用
#include <stdio.h>
void add(int a, int b);
int main(void)
{
add(1, 2);
return 0;
}
void add(int a, int b)
{
printf("a + b = %d", a + b);
}
exit函数
- 头文件
stdlib
- exit()
退出当前程序
- return
底层实质上在调用_exit()方法
main函数
不带参的main函数
int main(void);
int main();
带参的main函数
int main(int argc, char * argv[]);
int main(int argc, char ** argv);
#include<stdio.h>
int main(int argc, char* argv[])
{
for (size_t i = 0; i < argc; i++)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
1. gcc test.c -o test.exe
2. test.exe aa bb cc dd ee
右键项目 -> 属性 -> 调试 -> 命令参数
函数与指针
当函数调用时,系统会在stack空间中申请一块栈帧内存区域,可以存放形参与局部变量,以用来提供函数调用
当函数调用时,这块栈帧内存区域也会被自动释放
#include<stdio.h>
void swap(int, int);
int main(void)
{
int m = 1;
int n = 2;
printf("m = %d, n = %d\n", m, n);
swap(m, n);
printf("m = %d, n = %d\n", m, n);
return 0;
}
void swap(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
#include<stdio.h>
void swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int main(void)
{
int m = 1;
int n = 2;
printf("m = %d, n = %d\n", m, n);
swap(&m, &n);
printf("m = %d, n = %d\n", m, n);
return 0;
}
- 传递数组
当数组作为参数时,传递给函数的不是整个数组,而是数组的首地址,所以此时在函数中对形参进行sizeof计算,得到的是一个指针的大小,而非整个数组的大小,也因此,当需要传递数组时,通常会额外的添加一个形参,用于接收数组的长度
#include<stdio.h>
void bubbleSort(int arr[], int length)
{
int* inner;
for (size_t i = 0; i < length - 1; i++)
{
inner = arr;
for (size_t j = 0; j < length - i - 1; j++, inner++)
{
if (arr[j] > arr[j + 1])
{
int tmp;
tmp = *inner;
*inner = *(inner+1);
*(inner + 1) = tmp;
}
}
}
}
int main(void)
{
int arr[10] = { 2, 5, 9, 1, 4, 3, 7, 6, 10, 8 };
int length = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, length);
for (size_t i = 0; i < length; i++)
{
printf("arr[%d] = %d \n", i, arr[i]);
}
return 0;
}
C语言不允许返回数组
不要返回函数内部的局部变量,因为函数结束后栈帧被释放,其中的局部变量将随时被系统分配到其他地方使用
#include<stdio.h>
int m = 10000;
int* ret_point_test()
{
return &m;
}
int main(void)
{
int* p;
p = ret_point_test();
printf("p = %d\n", *p);
return 0;
}
多文件编程
将多个含有不同功能.c文件模块,编译到一起,生成一个可执行文件
int add(int a, int b)
{
return a + b;
}
- 头文件守卫
用于防止同一头文件在单个CPP/C文件中被重复分析
#pargma once
#ifndef __DEMO_H__
#define __DEMO_H__
#endif
- 头文件内容
#ifndef __DEMO_H__
#define __DEMO_H__
#include <stdio.h>
int add(int a, int b);
#endif
#include "demo.h"
int main(void)
{
int res = add(1, 2);
printf("1 + 2 = %d", res);
return 0;
}
指针
指针和内存单元
- 指针
即内存地址
- 内存单元
计算机中内存的最小存储单位,大小为一个字节
- 内存单元与指针的关系
每个内存单元都对应有一个惟一编号,该编号就称为该内存单元的地址
- 指针变量
即存储指针的变量
指针的定义和使用
int m = 10;
int n = 20;
n = m;
int a = 10;
int *p = &a;
*p = 20;
printf("a = %d", a);
int aa = 10;
int *pp = &aa;
aa = 20;
printf("*pp = %d", *pp);
指针的大小
指针的大小与数据类型无关,只与操作系统位数有关,即32位的系统,指针为4字节;64位操作为8字节
printf("%u\n", sizeof(void*));
printf("%u\n", sizeof(char*));
printf("%u\n", sizeof(short*));
printf("%u\n", sizeof(int*));
printf("%u\n", sizeof(long*));
printf("%u\n", sizeof(long long*));
printf("%u\n", sizeof(float*));
printf("%u\n", sizeof(double*));
野指针与空指针
- 没有一个有效空间地址的指针称为为野指针
int main(void)
{
int* p;
*p = 10000;
return 0;
}
- p,即指针有一个值,但是该值是不可访问的,则此时p也是个野指针
int main(void)
{
int* p = 10;
*p = 10000;
return 0;
}
- 杜绝使用野指针 – 空指针
int main(void)
{
int* p = NULL;
if (p != NULL)
{
*p = 10000;
}
return 0;
}
泛型指针
可以接收任意一种变量地址,但是在使用的时候必须进行强制类型转换,转换为对应的数据类型
int main(void)
{
void* p1, * p2;
int a = 10;
char ch = 'R';
p1 = &a;
p2 = &ch;
printf("p1 = %d\n", *((int *)p1));
printf("p2 = %c\n", *((char *)p2));
return 0;
}
指针和数组
- 数组名是地址常量,即不可被修改
&arr = arr - 指针是变量,所以可以用数组名给指针赋值
- 取数组元素的几种方式
int main(void)
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int* p = arr;
int length = sizeof(arr) / sizeof(arr[0]);
for (size_t i = 0; i < length; i++)
{
printf("方式一:%d\n", arr[i]);
printf("方式二:%d\n", *(arr + i));
printf("方式三:%d\n", p[i]);
printf("方式四:%d\n", *(p + i));
}
return 0;
}
- 指针是变量,数组名为常量
- sizeof
sizeof(指针)
sizeof(数组)
指针与const
int main(void)
{
const int a = 10;
int *p = &a;
*p = 20;
printf("a = %d\n", a);
return 0;
}
int main(void)
{
}
常用语函数形参,如
int fputs(const char * str, FILE * stream);
防止待输出的字符串str被中途篡改
指针类型的作用
如有数据:
int a = 0x12345678;
其在内存的存储情况如下:
1. int类型共占有4个字节,对应四个连续的地址0xff00到0xff03,并以首地址0xff00为变量a地址
2. 一个16进制对应4个二进制位,所以两个16进制数就
78
56
34
12
0xff00
0xff01
0xff02
0xff03
指针类型的作用:
- 决定了指针从存储地址开始的读取的字节数,如int一次读取4个字节
- 决定了指针变量做加/减1运算时偏移的字节数,如int一次偏移4个字节
int main(void)
{
int a = 0x12345678;
int* p1 = &a;
short* p2 = &a;
char* p3 = &a;
printf("*p1 = %p\n", *p1);
printf("*p2 = %p\n", *p2);
printf("*p3 = %p\n", *p3);
printf("&a = %p\n", &a);
printf("p1 = %p\n", p1);
printf("p1 + 1 = %p\n", p1 + 1);
printf("p2 = %p\n", p2);
printf("p2 + 1 = %p\n", p2 + 1);
return 0;
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8 ,9};
int* p = &arr;
int length = sizeof(arr) / sizeof(arr[0]);
for (size_t i = 0; i < length; i++, p++)
{
printf("arr[%d] = %d\n", i, *p);
}
printf("p - arr = %d", p - arr);
return 0;
}
指针的算术运算
- 指针与整数的乘除取余操作:error,即无法进行
- 指针与整数的加减操作
- 普通指针变量的加减
char * p; p + 1;
- 在数组中的加减
int arr[] = {1, 3, 5};
int *p = arr;
p + 1;
int main(void)
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 ,9 };
printf("arr = %p\n", arr);
printf("&arr[0] = %p\n", &arr[0]);
printf("arr + 1 = %p\n", arr + 1);
printf("&arr[1] = %p\n", &arr[1]);
printf("&arr = %p\n", &arr);
printf("&arr + 1 = %p\n", &arr + 1);
return 0;
}
- 加、乘除、取余:error,即禁止操作
- 减法
int main(void)
{
int a = 10;
int b = 20;
int* pa = &a;
int* pb = &b;
printf("pa = %p\n", pa);
printf("pb = %p\n", pb);
printf("pa - pb = %p\n", pa - pb);
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int* p = &arr[3];
int* q = &arr[6];
printf("q - p = %d", q - p);
return 0;
}
指针的比较运算
- 地址之间可以进行比较大小(> < =)
- 普通变量的比较没有意义,主要用于数组,可以将地址视为数字,即比较这个数字的大小,大的表示在更高位
- p != NULL,即常用语是否为空指针的比较
指针实现的strlen函数
int mystrlen(const char str[]);
int main(void)
{
char str[] = "hello world";
int len = mystrlen(str);
printf("length is %d\n", len);
return 0;
}
int mystrlen(const char str[]) {
char* p = str;
while(*p != '\0')
{
p++;
}
return p - str;
}
指针数组
- 指针数组本质就是一个二级指针:**arr
int main(void)
{
int a = 10;
int b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;
int* arr[] = { pa, pb, pc };
printf("*(arr[0]) = %d\n", *(arr[0]));
printf("*(*(arr + 0)) = %d\n", *(*(arr + 0)));
printf("**arr = %d\n", **arr);
return 0;
}
- 二维数组本质也是个二级指针
int main(void)
{
int a[] = { 10 };
int b[] = { 20 };
int c[] = { 30 };
int* arr[] = { a, b, c };
printf("arr[0][0] = %d\n", arr[0][0]);
printf("*((*(arr + 0) + 0)) = %d\n", *((*(arr + 0) + 0)));
printf("**arr = %d\n", **arr);
return 0;
}
多级指针
int main(void)
{
int a = 10;
int* p = &a;
int** p2 = &p;
int*** p3 = &p2;
p3 == &p2;
*p3 == p2 == &p;
**p3 == *p2 == p == &a;
***p3 == **p2 == *p == a;
return 0;
}
字符串
指针与字符串
char str1[] = {'h', 'i', '\0'};
char str2[] = "hi";
char * str3 = "hi";
char * str = "hi";
str1[0] = "H";
str3[0] = "H";
printf("abc");
常用字符串函数
strcmp
比较两个字符串大小,即比较两个字符串的每个字符,从第一个字符开始,如果一样则继续比下一个,最终都一样则返回0,如果不一样,则比较此时字符的ascii值,前者大则返回1,否则返回-1
#include<stdio.h>
int myStrcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)
{
if ('\0' == *str1) return 0;
str1++;
str2++;
}
return *str1 > * str2 ? 1 : -1;
}
int main(void)
{
char* str1 = "hello109213jkl";
char* str2 = "hello12";
int ret = myStrcmp(str1, str2);
if (ret == 0) printf("str1 == str2");
else if (ret > 0) printf("str1 > str2");
else printf("str1 < str2");
return 0;
}
strncmp
int myStrcmp(const char* str1, const char* str2, size_t n);
同strcmp ,但是可以指定只比较前n个字符
strcpy
将源字符串的内容拷贝到目标字符串中
#include<stdio.h>
void myStrcpy(const char* src, char* dst)
{
while (*src)
{
*dst = *src;
src++;
dst++;
}
*dst = '\0';
}
int main(void)
{
char* src = "hell0 world.";
char dst[15];
myStrcpy(src, dst);
printf("dst = %s", dst);
return 0;
}
dest的存储空间存在小于src空间的情况,因此此方法是非安全的,对此,可以使用安全的 strncpy 方法
strncpy
同strcpy
char* strncpy(char* dest, const char* src, size_t n);
strstr
char* strstr(char* str, char* substr);
获取substr在str中首次出现的位置到str末尾 的 这部分字符串,若不存在,则返回null
#include<stdio.h>
#include<string.h>
int main(void)
{
char* res = strstr("helloabc", "ll");
printf("res = %s\n", res);
return 0;
}
#include<stdio.h>
#include<string.h>
int substrCount(const char* str, const char* substr)
{
int res = 0;
char* p = strstr(str, substr);
while (NULL != p) {
res++;
p += strlen(substr);
p = strstr(p, substr);
}
return res;
}
int main(void)
{
int res = substrCount("helloworld", "l");
printf("res = %d\n", res);
return 0;
}
strchr
char* strchr(char* str, int c);
同strstr,只不过找的是一个字符
#include<stdio.h>
#include<string.h>
int main(void)
{
char* res = strchr("hello", 'l');
puts(res);
return 0;
}
strrchr
同strchr
同strstr,只不过从右边开始找
#include<stdio.h>
#include<string.h>
int main(void)
{
char* res = strrchr("hello", 'l');
puts(res);
return 0;
}
strcat
char* strcat(char* dest, const char* src);
将src拼接到dest后面,并返回拼接后的结果
(char* dest[10])存在空间不足的情况,所以需要用户使用时自行确保空间充足
strncat
char* strncat(char* dest, const char* src, size_t n);
同strcat ,但可以指定只拼接的src的前n个长度的字符
同strcat
sprintf
int sprintf(char* str, const char* format, ...);
相当于java的String.format,最终结果存入到第一个参数str中
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main(void)
{
char str[6] = {0};
sprintf(str, "%d%c%d=%d", 1, '+', 1, 1 + 1);
puts(str);
return 0;
}
sscanf
int sscanf(const char* str, const char* format, ...);
相较于scanf ,将原来从屏幕输入的格式化字符串,变为从第一个参数str中获取
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main(void)
{
char str[] = "1+1=2";
int a, b, c;
int res = sscanf(str, "%d+%d=%d", &a, &b, &c);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
strtok
char* strtok(char* str, const char* delim);
字符串分割,函数会找到str中首次出现的delim中的任意一个字符,并将原串,即str中该字符替换为\0,然后返回str的首地址,这样接得到str首地址到\0 的这部分 字符串了
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main(void)
{
char str[] = "abc.com.cn";
char* res = strtok(str, ".");
printf("res = %s\n", res);
return 0;
}
“abc de f.com%100.cn p”,获取abc de f com 100 cn p
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main(void)
{
char* res[50] = {NULL};
char** p = &res;
char str[] = "abc de f.com%100.cn p";
char* ret = strtok(str, ". %");
*p = ret;
p++;
while (NULL != ret)
{
ret = strtok(NULL, ". %");
*p = ret;
p++;
}
for (size_t i = 0; i < 10; i++)
{
if (NULL != res[i])
{
printf("res[%d] = %s\n", i, res[i]);
}
}
return 0;
}
atoi & atof & atol
int atoi(const char* nptr);
float atof(const char* nptr);
long atol(const char* nptr);
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(void)
{
char* str1 = "123";
int num1 = atoi(str1);
printf("num1 = %d\n", num1);
char* str2 = "0.123f";
float num2 = atof(str2);
printf("num2 = %.2lf\n", num2);
char* str3 = "123L";
long num3 = atol(str3);
printf("num3 = %ld\n", num3);
return 0;
}
内存管理
作用域
c语言将变量的作用域分为三种
- 代码块作用域(即{}范围内的)
- 函数作用域
- 文件作用域
局部变量
也叫auto 自动变量(auto可写可不写),一般情况下,代码块{} 内部定义的变量都是自动变量
- 在一个函数内定义,只在该函数范围内有效
- 在复合语句中定义,只在该复合语句中有效
- 随着函数调用的结束或复合语句的结束,局部变量的生命周期也随之结束
- 如果没有被赋予初值,系统会赋予一个随机值
从定义变量开始,到所属函数对应的栈帧被释放为止
全局变量
- 定义在函数外,可以被本(.c)文件,以及其他文件中的函数共同使用(注意,如果是其他文件中的函数调用,需要使用
extern 关键字声明) - 全局变量的生命周期同程序的运行周期
- 不同文件的全局变量也不可以重名
- 如果没有被赋予初值,int类型的赋予0值,其他类型类似
从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间
static局部变量
作用域依旧是局部代码块,但是只会定义一次,且定义的位置是在全局位置,通常用来做计数器
#include<stdio.h>
void test()
{
static int n = 0;
printf("n = %d\n", ++n);
}
int main(void)
{
for (size_t i = 0; i < 5; i++)
{
test();
}
return 0;
}
从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间
static全局变量
static int a = 10; 也是定义在函数外,但是经过static关键字修饰后,本文件中的全局变量a,只限制于在本文件中使用,即在其他文件中无法通过extern关键字进行声明与使用。
从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间
extern全局变量声明
extern int a; 只是声明一个变量,该变量(a)已经在别的文件中定义过了,这里只是声明的作用
全局函数与静态函数
在C语言中函数默认都是全局的,使用关键字static 可以将函数声明为静态,意味着该函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数。
无论是全局函数还是静态函数,也一样都是从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间
内存4区模型
windows
linux
.text 和 .rodata 链接后会合并,赋予只读权限 .data 和 .bss 链接后会合并,赋予读写权限
常用函数
memset
void *memset(void *s,int c,size_t n)
#include<stdio.h>
#include<string.h>
int main(void)
{
int arr[] = { 1, 2, 3 };
memset(arr, 1, 3);
for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("arr[%d] = %d \n", i, arr[i]);
}
char str1[] = { 'a', 'b', 'c', '\0' };
memset(str1, 'i', 2);
puts(str1);
char str2[] = "abc";
memset(str2, 'u', sizeof(str2) - 1);
printf("%s", str2);
return 0;
}
memcpy
void *memcpy(void *str1, const void *str2, size_t n)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main(void)
{
char str[] = "abc\0def";
printf("sizeof(str) = %lu \n", sizeof(str));
char cpy1[10];
char cpy2[10];
strncpy(cpy1, str, sizeof(str));
memcpy(cpy2, str, sizeof(str));
printf("strnpy: %s\n", cpy1 + strlen("abc") + 1);
printf("memcpy: %s\n", cpy2 + strlen("abc") + 1);
int a[5] = { 1, 2, 3, 4, 5 };
int b[5];
memcpy(b, a, sizeof(a));
return 0;
}
memmove
参见memcpy ,用于补足内存重叠情形
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main(void)
{
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memmove(&a[2], a, 5 * sizeof(int));
for (size_t i = 0; i < sizeof(a)/sizeof(a[0]); i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
memcmp
用于两个值的比较(对于数组从第一个元素逐个向后比较)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main(void)
{
int a[] = { 1, 2, 3, 4, 5 };
int b[] = { 1, 3, 2 };
int res = memcmp(a, b);
if (res > 0)
{
printf("a > b");
}
else if (res < 0)
{
printf("a < b");
}
else {
printf("a == b");
}
return 0;
}
堆空间的使用
malloc & free
void* malloc(size_t size);
void free(void* ptr);
#include<stdio.h>
int main(void)
{
int* p = (int*)malloc(10 * sizeof(int));
int* tmp = p;
for (size_t i = 0; i < 10; i++)
{
p[i] = i + 10;
}
for (size_t i = 0; i < 10; i++)
{
printf("p[%d] = %d\n", i, *(p+i));
}
free(tmp);
tmp = NULL;
return 0;
}
二级指针malloc空间
#include<stdio.h>
int main(void)
{
int** p = malloc(sizeof(int*) * 3);
for (size_t i = 0; i < 3; i++)
{
p[i] = malloc(sizeof(int) * 5);
}
for (size_t i = 0; i < 3; i++)
{
for (size_t j = 0; j < 5; j++)
{
p[i][j] = i + j;
}
}
for (size_t i = 0; i < 3; i++)
{
for (size_t j = 0; j < 5; j++)
{
printf("p[%d][%d] = %d\n", i, j, *(*(p + i) + j));
}
printf("\n");
}
for (size_t i = 0; i < 3; i++)
{
free(p[i]);
p[i] = NULL;
}
free(p);
p = NULL;
return 0;
}
复杂数据类型
结构体
结构体说明
struct Student
{
int age;
char name[100];
int score;
};
结构体示例
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct Student
{
char name[100];
int age;
int score;
};
struct Student2
{
char name[100];
int age;
int score;
} s1 = {"zhangsan", 18, 100}, s2;
struct
{
char name[100];
int age;
int score;
} s3, s4;
int main(void)
{
struct Student stu;
struct Student stu1 = { "张三", 18, 100 };
struct Student stu11;
stu11.age = 18;
strcpy(stu11.name, "李四");
stu11.score = 100;
struct Student *stu12;
stu12 = &stu;
stu12->age = 19;
strcpy(stu12->name, "王五");
stu12->score = 100;
struct Student stus999;
stus999 = stu11;
return 0;
}
结构体数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct Stu
{
char name[100];
int age;
};
int main(void)
{
struct Stu stus1[5] =
{
{"zhangsan", 1},
{"lisi", 2},
{"wangwu", 3},
{"zhaoliu", 4},
{"xiaoming", 5}
};
struct Stu stus2[5];
strcpy(stus2[0].name, "zhangsan");
stus2[0].age = 12;
strcpy((stus2 + 1)->name, "lisi");
(stus2 + 1)->age = 19;
strcpy((*(stus2 + 2)).name, "wangwu");
(*(stus2 + 2)).age = 20;
struct Stu* p = stus2;
strcpy((p + 3)->name, "zhaoliu");
(p + 3)->age = 21;
strcpy(p[4].name, "xiaoming");
p[4].age = 21;
for (size_t i = 0; i < sizeof(stus2)/sizeof(stus2[0]); i++)
{
printf("name=%s, age=%d \n", stus2[i].name, stus2[i].age);
}
return 0;
}
结构体嵌套结构体
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct BaseInfo
{
char name[100];
int age;
};
struct Player
{
struct BaseInfo info;
int score;
};
int main(void)
{
struct Player player1;
player1.info.age = 20;
strcpy(player1.info.name, "zhangsan");
player1.score = 100;
struct Player* player2 = &player1;
strcpy(player2->info.name, "lisi");
player2->info.age = 21;
player2->score = 90;
struct Player player3 = { "wangwu", 22, 91 };
printf("%s, %d, %d", player2->info.name, player2->info.age, player2->score);
return 0;
}
结构体指针成员
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct A
{
char* name;
};
int main(void)
{
struct A a;
strcpy(a.name, "abc");
return 0;
}
int main(void)
{
struct A a;
a.name = "abc";
return 0;
}
int main(void)
{
struct A a;
char temp[100];
a.name = temp;
strcpy(a.name, "abc");
puts(temp);
return 0;
}
#include<stdlib.h>
int main(void)
{
struct A a;
a.name = (char*)malloc((strlen("abc") + 1) * sizeof(char));
if (a.name != NULL)
{
strcpy(a.name, "abc");
puts(a.name);
}
if (a.name != NULL)
{
free(a.name);
a.name = NULL;
}
return 0;
}
int main(void)
{
struct A *a;
a = (struct A*)malloc(sizeof(struct A));
a->name = (char*)malloc((strlen("abc") + 1) * sizeof(char));
strcpy((*a).name, "abc");
printf("name=%s\n", a->name);
if (a->name != NULL)
{
free(a->name);
a->name = NULL;
}
if (a != NULL)
{
free(a);
a = NULL;
}
return 0;
}
结构体嵌套二级指针
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
typedef struct Teacher
{
char* name;
char** students;
} Teacher;
void allocateSpace(Teacher*** teachers)
{
if (NULL == teachers) return;
Teacher** tArray = malloc(sizeof(Teacher*) * 3);
for (size_t i = 0; i < 3; i++)
{
tArray[i] = malloc(sizeof(Teacher));
tArray[i]->name = malloc(sizeof(char) * 64);
sprintf(tArray[i]->name, "Teacher_%d", i + 1);
tArray[i]->students = malloc(sizeof(char*) * 5);
for (size_t j = 0; j < 5; j++)
{
(tArray[i]->students)[j] = malloc(sizeof(char) * 64);
sprintf((tArray[i]->students)[j], "%s_Student_%d", tArray[i]->name, j + 1);
}
}
*teachers = tArray;
}
void showSpace(Teacher** teachers)
{
if (NULL == teachers) return;
for (size_t i = 0; i < 3; i++)
{
puts(teachers[i]->name);
for (size_t j = 0; j < 5; j++)
{
printf(" %s\n", teachers[i]->students[j]);
}
}
}
void freeSpace(Teacher** teachers)
{
if (NULL == teachers) return;
for (size_t i = 0; i < 3; i++)
{
if (NULL != teachers[i]->name)
{
free(teachers[i]->name);
teachers[i]->name = NULL;
}
for (size_t j = 0; j < 5; j++)
{
if (NULL != teachers[i]->students[j])
{
free(teachers[i]->students[j]);
teachers[i]->students[j] = NULL;
}
}
if (NULL != teachers[i]->students)
{
free(teachers[i]->students);
teachers[i]->students = NULL;
}
if (NULL != teachers[i])
{
free(teachers[i]);
teachers[i] = NULL;
}
}
if (NULL != teachers)
{
free(teachers);
teachers = NULL;
}
}
int main(void)
{
Teacher** teachers = NULL;
allocateSpace(&teachers);
showSpace(teachers);
freeSpace(teachers);
return 0;
}
结构体拷贝
结构体字节对齐
#include<stdio.h>
struct Demo1
{
int a;
char b;
};
int main(void)
{
printf("Demo1: %lu", sizeof(struct Demo1));
return 0;
}
- 平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因(空间换时间)
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问 如上图,在32位操作系统中,数据总线是32位,所以一次读写4字节,如果未对齐,则对于其中的i(int),需要读取两次才能确定数值,而对齐后只需一次
- 第一个成员偏移量为0
- 其余成员偏移量为对齐数整数倍
- 对齐数 =
min ("成员中字节数最大者字节数", #pragma pack(指定的对齐数) ) - 如果有嵌套结构体,对齐数取内外层结构体中的较大者
#include<stdio.h>
struct Demo1
{
short a;
char b;
};
int main(void)
{
printf("Demo1: %lu", sizeof(struct Demo1));
return 0;
}
#include<stdio.h>
struct Demo2
{
short a;
double b;
char c[2];
};
int main(void)
{
printf("Demo2: %lu", sizeof(struct Demo2));
return 0;
}
#include<stdio.h>
int main(void)
{
typedef struct Demo1
{
double a1;
short b1;
} Demo1;
struct
{
char a2;
Demo1 b2;
int c2;
} Demo2;
printf("Demo2: %lu", sizeof(Demo2));
return 0;
}
- 对齐数 > 最大成员字节数
取最大成员字节数为实质对齐数
#include<stdio.h>
#pragma pack(8)
int main(void)
{
struct
{
char a;
int b;
} Demo;
printf("Demo: %lu", sizeof(Demo));
return 0;
}
- 对齐数 < 最大成员字节数
字节数超过对齐数的成员计算偏移量时以对齐数进行计算
#include<stdio.h>
#pragma pack(2)
int main(void)
{
struct
{
char a;
int b;
} Demo;
printf("Demo: %lu", sizeof(Demo));
return 0;
}
结构体位段
结构体中允许存在位段、无名字段以及字对齐所需的填充字段。这些都是通过在字段的声明后面加一个冒号以及一个表示字段位长的整数来实现。这些冒号后的整数规定了成员所占的位数
#include<stdio.h>
int main(void)
{
struct
{
char a : 4;
int b : 8;
} Demo;
Demo.a = 0xf0;
Demo.b = 257;
printf("a = %d\n", Demo.a);
printf("b = %d\n", Demo.b);
return 0;
}
如果结构体中相连且相同类型的成员指定了位段,且位段总和小于其原始类型字节数,则会视情况将这些成员压缩到一个字节类型空间中
#include<stdio.h>
int main(void)
{
struct
{
int a : 4;
int b : 8;
} Demo;
printf("Demo: %lu", sizeof(Demo));
return 0;
}
共用体(联合体)
- 所有成员公用相同的内存地址
- 大小为成员最大的字节数
- 由于公用内存,所以改动一个成员,其余成员将受影响
#include<stdio.h>
int main(void)
{
union
{
unsigned char a;
unsigned int b;
unsigned short c;
} Demo;
printf("大小为成员中字节数最大的:%lu\n", sizeof(Demo));
Demo.b = 0x11223344;
printf("a=%x\n", Demo.a);
printf("b=%x\n", Demo.b);
printf("c=%x\n", Demo.c);
Demo.a = 0xff;
printf("a=%x\n", Demo.a);
printf("b=%x\n", Demo.b);
printf("c=%x\n", Demo.c);
return 0;
}
枚举
#include<stdio.h>
enum Color
{
GREEN,
YELLOW,
RED=10,
BLUE,
PINK
};
int main(void)
{
enum Color flag = GREEN;
enum Color flag2 = 1;
printf("%d, %d, %d, %d, %d", GREEN, YELLOW, RED, BLUE, PINK);
return 0;
}
文件处理
概述
typedef struct
{
short level;
unsigned flags;
char fd;
unsigned char hold;
short bsize;
unsigned char * buffer;
unsigned ar;
unsigned istemp;
short token;
} FILE;
FILE * fp;
- 对于各平台,结构体FILE内部的成员变量不尽一致,但是结构体变量名称均为FILE
- 只要fp调用了
fopen() 函数,该函数就会在堆区申请空间并将地址返回给fp - 并非是fp关联文件,而是内部成员保存了文件的相关信息
- 不要直接操作fp指针,而是通过相关函数,函数的调用会自动调整fp中成员的相关值
- 文件描述符,每打开一个文件对应就给这个文件一个int值,用于标识,在linux中可以使用
ulimit -a 命令,其中open files 就是描述符取值范围,其中0(stdin), 1(stdout), 2(stderr) 三个值默认为系统占用
三大特殊文件指针
C语言中有三个特殊的文件指针由系统默认打开,无需用户定义即可直接使用
stdin
标准输入,默认为当前终端(键盘),scanf, getchar等函数默认从此终端获得数据
stdout
标准输出,默认为当前终端(屏幕),printf, puts等函数默认输出数据到此终端
stderr
标准出错,默认为当前终端(屏幕),perror函数默认输出数据到此终端
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main(void)
{
int a;
printf("请输入:");
scanf("%d", &a);
printf("a = %d\n", a);
fclose(stdin);
scanf("%d", &a);
perror("stdin error");
fclose(stdout);
printf("=========");
perror("stdout error: ");
fclose(stderr);
perror("stderr error: ");
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(void)
{
printf("aaaaaaa");
_close(1);
int fd = _open("test.txt", O_WRONLY, 0777);
printf("fd = %d\n", fd);
printf("bbbbbbb");
return 0;
}
VS环境下的相对路径
- 如果是编译运行,则相对位置起点是
项目名称.vcxproj 所在路径; - 如果是直接双击
xxx.exe ,则是xxx.exe 所在路径
常用函数
fopen & fclose
FILE * fopen(const char* filename, const char * mode);
int fclose(FILE * strean);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
FILE* fp = fopen("test.txt", "r");
if (NULL == fp)
{
perror("fopen error");
return -1;
}
fclose(fp);
return 0;
}
fgetc & fputc
int fputc(int ch, FILE * stream);
int fgetc(FILE * stream);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
FILE* fp = fopen("test.txt", "w");
if (NULL == fp)
{
perror("fopen error");
return -1;
}
int ret = fputc('A', fp);
printf("ret = %d", ret);
fclose(fp);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
FILE* fp = fopen("test.txt", "r");
if (NULL == fp)
{
perror("fopen error");
return -1;
}
while (1)
{
int ch = fgetc(fp);
if (EOF == ch)
{
printf("end ------- ");
break;
}
printf("ch = %c\n", ch);
}
fclose(fp);
return 0;
}
fgets & fputs
int fputs(const char* str, FILE * stream);
char * fgets(char* s, int size, FILE * stream);
int main(void)
{
char buf[10] = { 0 };
fgets(buf, 10, stdin);
printf("buf: %s", buf);
FILE* fp = fopen("test.txt", "w");
fputs(buf, fp);
fclose(fp);
return 0;
}
fprintf & fscanf
int fprintf(FILE * stream, const char * format, ...);
int fscanf(FILE* stream, const char * format, ...);
fprintf(fp, "%d = %d %c %d\n", 15, 3, '*', 5);
int main(void)
{
FILE* fp = fopen("test.txt", "r");
int sum, a, b;
char operate;
int count = fscanf(fp, "%d=%d%c%d", &sum, &a, &operate, &b);
fclose(fp);
printf("count=%d\n", count);
printf("%d=%d%c%d", sum, a, operate, b);
return 0;
}
fread & fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream);
size_t fread(void * ptr, size_t size, size_t nmemb, FILE * stream);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct Demo
{
int age;
char name[10];
int number;
} Demo;
int main(void)
{
printf("sizeof(Demo) = %lu\n", sizeof(Demo));
Demo arr[3] = {
{18, "zhangsan", 9},
{21, "lisi", 12},
{20, "wangwu", 10}
};
FILE* fp = fopen("test.txt", "w");
if (!fp)
{
perror("fopen error");
return -1;
}
int bytes = fwrite(&arr[0], 1, sizeof(Demo) * 3, fp);
printf("bytes=%d\n", bytes);
fclose(fp);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct Demo
{
int age;
char name[10];
int number;
} Demo;
int main(void)
{
FILE* fp = fopen("test.txt", "r");
if (!fp)
{
perror("fopen error");
return -1;
}
Demo arr[3];
Demo* p = arr;
while (1)
{
int ret = fread(p++, 1, sizeof(Demo), fp);
printf("ret=%d\n", ret);
if (ret == 0)
{
break;
}
}
fclose(fp);
Demo temp;
for (size_t i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
{
temp = *(arr + i);
printf("%d: name=%s, age=%d, num=%d\n", i, temp.name, temp.age, temp.number);
}
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void mycopy()
{
FILE* rfile = fopen("C:\\Users\\ysw15\\Pictures\\Saved Pictures\\帅.jpg", "rb");
FILE* wfile = fopen("C:\\Users\\ysw15\\Pictures\\Saved Pictures\\帅2.jpg", "wb");
int ret;
char buf[1024] = { 0 };
while (1)
{
ret = fread(buf, 1, sizeof(buf), rfile);
if (0 == ret)
{
break;
}
fwrite(buf, 1, ret, wfile);
}
fclose(rfile);
fclose(wfile);
}
int main(void)
{
mycopy();
return 0;
}
remove & rename
int remove(const char * pathname);
int rename(const char* oldpath, const char* newpath);
fseek & ftell & rewind
int fseek(FILE * stream, long, offset, int whence);
long ftell(FILE * stream);
void rewind(FILE * stream);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
typedef struct Student
{
int age;
char name[10];
int number;
} Student;
int main(void)
{
Student stus[4] =
{
{18, "zhangsan", 201},
{19, "lisi", 302},
{20, "wangwu", 109},
{21, "zhaoliu", 209}
};
FILE * fp = fopen("C:\\Users\\ysw15\\Pictures\\Saved Pictures\\test", "wb+");
fwrite(&stus[0], 1, sizeof(stus), fp);
fseek(fp, sizeof(Student) * 2, SEEK_SET);
Student wangwu;
fread(&wangwu, 1, sizeof(Student), fp);
printf("1. name=%s, age=%d, number=%d\n", wangwu.name, wangwu.age, wangwu.number);
int offset = ftell(fp);
printf("当前偏移量为:%d\n", offset);
rewind(fp);
Student zhangsan;
fread(&zhangsan, 1, sizeof(Student), fp);
printf("2. name=%s, age=%d, number=%d\n", zhangsan.name, zhangsan.age, zhangsan.number);
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
printf("文件的大小为:%d\n", size);
fclose(fp);
return 0;
}
stat
#include<sys/types.h>
#include<sys/stat.h>
int stat(const char* path, struct stat* buf);
struct stat
{
def_t st_dev;
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
off_t st_size;
unsigned long st_blksize;
unsigned long st_blocks; 块数
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(void)
{
struct stat buf;
int ret = stat("test.txt", & buf);
printf("file size is %d\n", buf.st_size);
return 0;
}
fflush
int fflush(FILE * stream);
feof
int feof(FILE * stream);
if (feof(fp))
{
fgetc(fp);
break;
}
WIndows和Linux的先读后写差异
对于先读后写的情况,在进行写入时,Windows下返回写入成功,但是实质上并未真正写入,此时需要添加额外的语句fseek(fp, 0, SEEK_CUR);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main(void)
{
FILE* fp = fopen("C:\\Users\\ysw15\\Pictures\\Saved Pictures\\test.txt", "rb+");
char buf[6] = { 0 };
char* ptr = fgets(buf, 6, fp);
printf("buf=%s, ptr=%s\n", buf, ptr);
fseek(fp, 0, SEEK_CUR);
int ret = fputs("AAAAA", fp);
printf("ret = %d", ret);
fclose(fp);
return 0;
}
几个函数与关键字
putchar
输出一个字符到屏幕上
putchar(97);
putchar('a');
puts
将一个字符串(只能是字符串)输出到屏幕上,自带换行符
scanf
接收键盘输入的数据
建议使用 scanf_s,因为scanf在读取时不会检查边界,存在造成内存访问越界的问题,如分配了5字节的空间,但是读入了10字节,多余的部分会被写入到别的空间上去,
char buf[6] = {'\0'};
scanf_s("%s", buf, 6)
#include <stdio.h>
int main(void)
{
char c1, c2, c3;
scanf_s("%c%c%c", &c1, 1, &c2, 1, &c3, 1);
printf("c1 = %c\n", c1);
printf("c2 = %c\n", c2);
printf("c3 = %c\n", c3);
int a1, a2, a3;
scanf_s("%d %d %d", &a1, 1, &a2, 1, &a3,1);
printf("a1 = %d\n", a1);
printf("a2 = %d\n", a2);
printf("a3 = %d\n", a3);
char str[6];
scanf_s("%s", str, 6);
printf("str = %s", str);
return 0;
}
在接收字符串时,无论是scanf还是scanf_s,除了敲击换行, 输入空格也会终止接收输入
int main(void) {
char str[11] = { 0 };
scanf("%[^\n]", str);
printf("res: %s", str);
return 0;
}
goto
#include <stdio.h>
int main(void)
{
printf("1\n");
goto tag;
printf("2\n");
tag:
printf("3\n");
}
- goto仅在当前函数中有效(可以在一个函数的两个for之间跳转)
#include <stdio.h>
int main(void)
{
int j = 1;
for (int i = 1; i <= 5; i++) {
if (i == 3) {
goto tag;
}
printf("i = %d\n", i);
}
for (; j <= 5; j++) {
tag:
printf("j = %d\n", j);
}
}
|