// 导入一个文件,std是一个标准库,io是输入输出
/*
<> 表示导入系统文件
"" 表示导入自定义的文件
*/
#include <stdio.h>
#include <stdlib.h> // 里面包含了system函数
int main() {
printf("Hello, World!\n"); // 打印输出hello world,行注释
system("pause"); // 按任意键继续
/* 块注释 */
return 0; // 表示函数的返回值
}
system:使用系统命令是,成功返回0
C 代码编译成可执行程序经过4步:
(1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
(2)编译:检查语法,将预处理后文件编译生成汇编文件
(3)汇编:将汇编文件生成目标文件(二进制文件)
(4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去
使用gcc查看编译过程
gcc -E hello.c -o hello.i # 预处理
gcc -S hello.i -o hello.s # 编译
gcc -c hello.s -o hello.o # 汇编
gcc hello.o -o hello.exe # 链接
:<<!
# 一步编译的代码:
!
gcc -o hello.exe h1.c h2.c ... # 后面可以编译多个代码,同时生成一个hello.exe 文件
-o:表示输出文件的地址
mov 移动
add 添加
push 入栈
pop 出栈
call 调用
eax 32位寄存器
关键字:C语言里面有32个关键字
数据类型
常量
定义方式
#define MAX 20
const int max_ = 23; // 不安全
变量
命名规则
变量特点
定义方式
int max_ = 23; // 其是可以改变的
| 占位符 | 含义 |
|---|---|
%d | 输出一个有符号的十进制整型数据 |
%o | 输出八进制的整型数据 |
%x | 输出十六进制的整型数据,字母以小写输出 |
%X | 输出十六进制的整型数据,字母以大写输出 |
%u | 输出一个十进制的无符号数 |
// 无符号 unsigned;有符号 signed
int a = -10; // 有符号代表有正负,默认为有符号
unsigned int b = 10; // 无符号数,只能为正数,计算结果也要为正数
int c = 0123; // 定义八进制数据,以 0 开头
int d = 0x234ba; // 定义十六进制数据,以 0x 开头
C 不能直接书写二进制数据的形式
例如:
#include <stdio.h>
int main() {
int a;
scanf_s("%d", &a); // 通过键盘输入赋值
printf_s("%d \n", a); // 输出值
return 0;
}
使用
scanf会出现安全问题,使用scanf_s,安全输入
使用不同的关键字定义整型,其开辟的空间是不一样的,所占字节数与所选择的操作系统有关,可以使用sizeof(int)来查看所占空间(BYTE)
字符型变量用于存储一个单一字符,在C 语言中用char表示,其中每个字符变量都会占用1个字节。在给字符型变量赋值时,需要使用单引号
字符型变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的acsii编码放到变量的存储单元中。char的本质就是一个1字节大小的整型
特殊字符:
char a = 'a';
#include <stdio.h>
int main() {
char a;
scanf_s("%c", &a); // 安全输入
printf_s("%c \n", a); // 安全输出
return 0;
}
浮点型变量也可以称为实型变量,浮点型变量是用来存储小数值的。在 C 语言中,浮点型变量分为两种:单精度浮点数(float)、双精度浮点数(double),但是double型变量所表示的浮点数比float型变量更准确
由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。在有效位以外的数字将被舍去,这样可能会产生一些误差。
不以f结尾的常量是double类型,以f结尾的常量(如:3.14f)是float类型
| 限定符 | 含义 |
|---|---|
extern | 声明一个变量,extern声明的变量没有建立存储空间 |
const | 定义一个常量,常量的值不能修改 |
Volatile | 防止编译器优化代码 |
register | 定义寄存器变量,提高效率 |
char空间,以'\0'结尾字符串常量和字符常量不同
'\0'定义
char* a = "hello"; // 使用指针来定义
char b[] = "hello"; // 使用字符数组来定义
printf("%s\n", a); // 输出字符串
printf是输出一个字符串,putchar输出一个字符
占位符:
| 格式 | 含义 |
|---|---|
| %a,%A | 浮点数、十六进制数字和p-计数法 |
| %c | 一个字符 |
| %C | 一个ISO宽字符 |
| %d | 有符号十进制整数(int)(%ld ,%Ld为:长整型数据,%hd:短整型数) |
| %e,%E | 浮点数,e-计数法,E-计数法 |
| %f | 单精度浮点数 |
| %g,%G | 根据数值不同自动选择%f或%e |
| %i | 有符号十进制数(与%d相同) |
| %o | 无符号八进制整数 |
| %p | 指针 |
| %s | 对应字符串char*(%s = %hs = %hS 输出 窄字符) |
| %S | 对应宽字符串WCAHR*(%ws = %S 输出宽字符串) |
| %u | 无符号十进制整数(unsigned int) |
| %x,%X | 使用十六进制数字0xf的无符号十六进制整 |
| %% | 打印一个%号 |
| %I64d | 用于int64 或者 long long |
| %I64u | 用于uint64 或者unsigned long long |
| %I64x | 用于64 位16进制数字 |
附加格式:
| 符号 | 含义 |
|---|---|
| - | 左对齐 |
| + | 右对齐 |
| .n | 对于小数点,保留n位小数 |
| # | 对c,s,d,u无影响,对o类输出前加缀为o,对x类,在输出前缀加0x,对e,g,f当结果有小数时给出小数点 |
| m | 代表数据的最小宽度 |
char ch = '0';
putchar(ch); // 输出一个字符
putchar('\n');
putchar(97);
输出字符可以是变量、字符、数字或者转义字符
getchar是从标准输入设备读取一个字符;scanf_s通过%转义的方式可以得到用户通过标准输入设备输入的数据
int a, b;
scanf_s("%d %d", &a, &b); // 输入两个整型数据,使用空格(分隔符)或换行
char ch;
ch = getchar(); // 只会接收一个字符,如果有其他的,可以使用while循环遍历,也可以用来作为暂时停留界面
putchar(ch);
数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题
自动转换:遵循一定的规则,由编译系统自动完成
char a = 2;
int c = a; // a 变成了int类型,有符号数会转换成无符号数
小范围的类型会自动转换成大范围的类型运算
把表达式的运算结果强制转换成所需的数据类型
数据类型2 变量2 = (数据类型2)变量1 // 其不会四舍五入,直接丢失后面的数据
其会造成精度的丢失
用于处理四则运算
+:加 -:减 *:乘 /:除 %:取余 ++:自增 --:自减
// 前自增先赋值,后运算;后自增相反
用于将表达式的值赋给变量
+= -= /= *= =
用于表达式的比较,并返回一个真值(true)或假值(false)
== != > < >= <=
用于根据表达式的值返回真值或假值
!:非 &&:与 ||:或
所有非零的值都是真值,非真即假
C/C++支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构
作用:执行满足条件的语句
if语句的三种形式
#include <stdio.h>
int main() {
// 选择结构 单行if语句
// 用户输入一个数字
int num = 0;
printf_s("请输入一个数字:");
scanf_s("%d", &num);
// 判断数字是否大于100,是则输出原数
if (num >= 100){ // 注意if语句后面不要加分号,否则,if将不会进行判断
printf_s("%d\n", num);
}
return 0;
}
#include <stdio.h>
int main() {
// 选择结构 单行if语句
// 用户输入一个数字
int num = 1;
printf_s("请输入一个数字:");
scanf_s("%d", num);
// 判断数字是否大于100,是则输出原数;否则,输出0
if (num >= 100) { // 注意if语句后面不要加分号,否则,if将不会进行判断
printf_s("%d", num);
}
else {
printf_s("%d", 0);
}
return 0;
}
#include <stdio.h>
int main() {
// 选择结构 单行if语句
// 用户输入一个数字
int num = 1;
printf_s("请输入一个数字:");
scanf_s("%d", num);
// 判断数字是否大于100,是则输出原数;否则,输出0
if (num >= 600) { // 注意if语句后面不要加分号,否则,if将不会进行判断
printf_s("%d", 600);
}
else if (num >= 100) { // 注意if语句后面不要加分号,否则,if将不会进行判断
printf_s("%d", num);
}
else {
printf_s("%d", 0);
}
return 0;
}
if ( 条件1 ) { 条件1满足执行语句} else if ( 条件2 ) { 条件2满足,同时条件1不满足,执行的语句 }··· else { 都不满足执行的语句 }
在if语句中,可以嵌套使用if语句,达到更加精确的条件判断
案例:输入3个数字,判断出最大的数字
#include <stdio.h>
int main() {
int num = 1;
int num1 = 1;
int num2 = 1;
printf_s("请输入三个数字:");
scanf_s("%d,%d,%d", &num, &num1, &num2);
if ( num > num1 ) { // 判断 num 和 num1
if ( num > num2 ) { // 判断 num 和 num2
printf_s("%d最大\n", num);
}
else {
printf_s("%d最大\n", num2);
}
}
else {
if (num1 > num2) {
printf_s("%d最大\n", num1);
}
else {
printf_s("%d最大", num2);
}
printf_s("判断完成");
}
return 0;
}
作用:通过三目运算实现简单的判断
语法:表达式1 ? 表达式2 : 表达式3
#include <stdio.h>
int main() {
// 三目运算
// 将 a 和 b 做比较,将大的值赋值给c
int a = 10;
int b = 20;
int c = 0;
c = a > b ? a : b; // 如果 a 比 b 大,则将a赋值给c
/*
if ( a > b ) {
c = a;
}
else {
c = b
}
*/
return 0;
}
在C++中,三目运算符返回的是变量,可以继续赋值
作用:执行多条件分支语句
语法:
switch ( 表达式 ) {
case 结果1: 执行语句; break; // switch里面不一定每个case都要对应break,break的作用的向外跳出一层
······
default: 执行语句; break; // 不一定需要default判断
}
示例
#include <stdio.h>
int main() {
// switch 语句
// 电影打分
int score = 0;
printf_s("请输入分数:");
scanf_s("%d", &score);
printf_s("您打的分数为:%d\n", score);
switch (score) {
case 10:
printf_s("是经典电影\n");
break; // 退出当前分支,如果没有break,则会继续向下运行
default: // 当所有条件不满足时,执行该语句
printf_s("普通\n");
}
}
缺点:判断的时候,只能是整型或者字符型,不可以是一个区间
优点:结构清晰,执行效率高
注意
- switch语句中表达式类型只能是整型或者字符型
- case里如果没有break,那么程序会一直向下执行
作用:满足循环条件,执行循环语句
语法:while ( 循环条件 ) { 循环语句 }
解释:只要循环条件的结果为真,就执行循环语句
#include <stdio.h>
int main() {
int nu = 0;
// 在屏幕中打印0到9的数字
while ( nu < 10 )
{
printf_s("%d\n",nu);
nu++;
}
}
作用:满足循环条件,执行循环语句
语法:do {循环语句} while (循环条件);
注意:与while的区别在于do…while会先执行一次循环时间,在判断循环条件
#include <stdio.h>
int main() {
// do...while语句
// 在屏幕中输出 0 到 9 这10个数字
int num = 0;
do {
printf_s("%d\n", num);
num++;
}
while (num <= 9);
}
作用:满足循环条件,执行循环语句
语法:for (起始表达式;条件表达式;末尾循环体) { 循环语句; }
#include <stdio.h>
int main() {
// for循环
// 从数字0打印到9
for (int i = 0; i < 10; i++ ) {
printf_s("%d\n", i);
}
// 也可以
int i = 0;
for (;;) {
if (i >= 10) {
break;
}
printf_s("%d\n", i++);
}
}
作用:在循环中在嵌套一层循环,解决一些实际问题
#include <stdio.h>
int main() {
// 嵌套循环
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
printf_s(" * ");
}
printf_s("\n");
}
}
作用:用于跳出选择结构或者循环结构
break使用的时机:
作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
#include <stdio.h>
int main() {
for (int i = 0; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
else {
ptintf_s("%d\t", i);
}
}
}
作用:可以无条件跳转语句
语法:goto 标记;
解释:如果标记的名称存在,执行到goto语句时,会跳转到标记的位置
#include <stdio.h>
int main() {
// goto
printf_s("hello\n");
printf_s("hello\n");
printf_s("hello\n");
goto flag;
printf_s("hello\n");
printf_s("hello\n");
flag:
printf_s("world\n");
}
在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组
数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的
数组属于构造数据类型
数组名是地址常量
int arr[] = { 1, 2, 4, 5, 6, 7 };
int arr1[6] = { 1, 2, 3, 4, 5, 6 };
int len = sizeof(arr) / sizeof(arr[0]); // 获取数组的长度
整型数组默认初始化的值为0,
作用:最常用的排序算法,对数组内的元素进行排序
#include <stdio.h>
int main() {
// 利用冒泡排序,实现升序排序
int arr[ ] = {4, 2, 8, 0, 5, 7, 1, 3, 9};
printf_s("排序前:");
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++) {
printf_s("%d\t", arr[i]);
}
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0]) - 1); i++) { // 排序次数为元素个数减一
for (int j = 0; j < (sizeof(arr) / sizeof(arr[0]) - 1 - i); j++) { // 内层循环对比次数元素个数 - 排序次数 - 1
if (arr[j] > arr[j + 1]) { // 如果前一个数字比第二个数字大,交换顺序
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
printf_s("\n排序后:");
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++) {
printf_s("%d\t", arr[i]);
}
}
二维数组定义的一般形式是:
类型说明符 数组名[常量表达式1][常量表达式2];
其中常量表达式1表示行数,常量表达式2表示列数
char arr[5] = { 'h', 'e', 'l', 'l', 'o' }; // 字符数组
char* a = "world"; // 字符串
如果字符数组有
'\0'的标志,则可以认为其是一个字符串,字符串是字符数组的特例
字符数组和字符串的区别:
char的数组来替代char的数组,但char的数组未必是字符串'\0'等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char数组char str[100];
scanf_s("%s", str); // 默认使用空格分隔
// gets_s()
char* gets_s(char* s);
// 功能:从标准输入读取字符,并保存到指定的内存空间,直到出现换行或读到文件结尾为止,成功返回字符串,失败返回空
// fgets_s()
char* fgets(char* s, int size, FILE* stream);
// 功能:从stream指定的文件中读入字符,保存到所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size-1个字符为止,最后会自动加上'\0',作为字符串结束,成功返回字符串,失败返回空。其通过键盘输入时,包括了最后的'\n'换行
scanf_s和gets_s的区别:
scanf_s不允许输入有空格;gets_s允许输入有空格
fgets里面的stream是文件操作指针
例如,获取键盘输入的字符串:
char ch[101];
sacnf_s("%s", ch);
gets_s(ch);
fgets(ch, sizeof(ch), stdin);
上面的代码都是获取从键盘输入的字符串
printf_s("%s\n", str);
int puts(const char* s);
// 功能:标准设备输出字符串,在输出完成后自动输出一个'\n'
int fputs(const char* s, FILE* stream);
// 功能:将s所指定的字符串写入到stream指定的文件中,字符串结束符'\0'不写入文件,同时不会自动换行
例如:
char c[] = "hello world";
puts(c);
fputs(c, stdout);
#include <string.h>
int strlen(const char* s);
// 功能:计算指定字符串的长度,不包括'\0'
C 程序是由函数构成的,我们写的代码都是由主函数 main() 开始执行的。函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。
从函数定义的角度看,函数可分为系统函数和用户定义函数两种:
从函数执行结果的角度来看, 函数可分为有返回值函数和无返回值函数两种
从主调函数和被调函数之间数据传送的角度看,又可分为无参函数和有参函数两种
当调用函数时,需要关心5要素:
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
int getRandNum() {
srand((unsigned int)time(NULL)); // 使用随机种子
// srand((size_t)time(NULL));
int n = rand() % 10; // 产生0到9的随机数
// rand() % (max - min + 1) + min 取得 min ~ max 的随机数
return n;
}
定义函数的目的
自定义函数的书写形式
返回值类型 函数名(参数类型 形参1, 参数类型 形参2, ...) {
函数体;
返回值;
}
int get_num(int a, int b) {
int sum = a + b;
return sum;
}
在函数调用过程中传递的参数称为实参,有具体的值
在函数的定义中参数称为形参,形式参数
在函数调用过程中实参传递给形参
在函数调用结束后,函数会在内存中销毁
理论上,函数名是可以随意起名字,见名知义,应该让用户看到这个函数名字就知道这个函数的功能。注意,函数名的后面要加括号,代表这个是函数,而不是普通的变量
在定义函数时,指定形参,在未出现函数调用时,它们并不占内存中的存储单元。因此,称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据,所以,形参里的变量不能赋值
int sum(int a, int b = 20); // 这会报错,形参在C语言里面不能赋值
在定义函数时指定的形参,必须是类型+变量的形式
int sum(int a, int b); // right
花括号里面的内容即为函数体的内容,这里为函数功能实现的过程,这和以前的写代码没太大区别,以前我们把代码写在main()函数里,现在只是把这个写到别的函数里面
函数的返回值是通过函数中的return语句获得的,return后面的值也可以是一个表达式
return语句中表达式的值和函数返回类型是同一类型return语句中表达式的值不一致,则以函数返回类型为准,即函数返回类型决定返回值类型。对数值型数据,可以自动进行类型转换。定义函数后,我们需要调用此函数才能执行到这个函数里面的代码段。这和main()函数不一样,main()为编译器设定好自动调用的主函数,无需人为调用,我们都是在main()函数里调用别的函数,一个C程序里有且只有一个main()函数
main函数test函数
main()函数的前面寻找有没有一个名字叫做test的函数定义test()函数,这时候,main()函数李曼的执行会阻停在test()这一行代码,等待函数执行完成main()函数继续执行#include <stdlib.h>
#include <stdio.h>
int getSum(int, int); // extern int getSum(int, int);
int main() {
int sum = getSum(1, 2);
printf_s("%d\n", sum);
system("pause");
return 0;
}
int getSum(int a, int b) {
return a + b;
}
从广义的角度来讲,声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义:
int b它既是声明,同时又是定义extern b来讲,它只是声明,不是定义一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”
一个文件声明函数
如,创建一个hello.h
#ifndef NEW_HELLO_H // 如果没有这个文件,可以防止头文件包含
// 也可以使用 #pragma once 来防止头文件包含只能在Windows中使用
#define NEW_HELLO_H
int getSum(int, int); // 可以把extern省略
#endif //NEW_HELLO_H
一个文件实现函数hello.c
#include "hello.h"
int getSum(int a, int b) {
return a + b;
}
一个文件调用函数main.c
#include <stdlib.h>
#include <stdio.h>
#include "hello.h"
int main() {
int sum = getSum(1, 2);
printf_s("%d\n", sum);
system("pause");
return 0;
}
编译代码
gcc -o hello.exe main.c hello.c hello.h
int main(int argc, const char * argv[]) {
printf_s("命令行传入参数的个数:%d,第一个参数为:%s\n", argc, argv[0]);
system("pause");
return 0;
}
main的含义:
return 0的含义:
main函数是否正确的被执行了main函数的执行正常,那么就返回0main函数的执行不正常,那么就返回一个非0的函数返回值类型
return 后面写的是什么类型,函数的返回值类型就必须是什么类型,所以写int形参列表的含义:
int argc
main函数时传递给argv的值的个数const char* argv[]
main函数执行文件的路径什么是递归函数?
递归函数构成条件
实例:求一个数的累加和
#include <stdlib.h>
#include <stdio.h>
int getSum(int n) {
return n == 1 ? 1 : getSum(n - 1) + n;
}
int main() {
int n;
scanf_s("%d", &n);
printf_s("累加和为:%d\n", getSum(n));
system("pause");
return 0;
}
递归和循环区别
内存含义:
内存是沟通CPU与硬盘的桥梁:
有关内存的两个概念:物理存储器和存储地址空间
物理存储器:实际存在的具体存储芯片
存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义
内存中的每一个数据都会分配相应的地址:
char:占一个字节分配一个地址int:占四个字节分配四个地址C 语言中把地址形象地称作指针
获取地址的方法:
int a = 10;
printf("%x\n", &a); // & 为取址运算符
可以保存地址值(指针)的变量称为指针变量,因为指针变量中保存的是地址值,故可以把指针变量形象地比喻成地址箱
// 定义一个指针变量
int* p;
int a = 10;
// 给指针变量赋值
p = &a;
使用取值运算符来修改指针变量所对应的值
printf("修改前:%d", a);
*p = 100;
printf("修改后:%d", a);
sizeof()测量指针的大小,得到的总是:4或8sizeof()测的是指针变量指向内存地址的大小printf("%d\n", sizeof(int*));
指针变量也是变量,是变量就可以任意赋值,不要越界即可,但是任何数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知的(操作系统不允许此指针指向内存区域)。所以,也指针不会直接引发错误,操作指针指向的内存区域才会出问题。
int* p = 100; // 野指针 -> 指针变量指未知的空间
// 操作系统将0~255作为系统占用空间,不允许访问操作
// 操作野指针对应的空间可能报错
printf("%d\n", *p);
但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量。C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。
int* p = NULL; // 空指针是指内存地址编号为0的内存空间
void*指针可以指向任意变量的内存空间:
int a = 10;
// 万能指针可以接收任意类型变量的内存地址
void* p = &a;
// *p = 100; // 报错了,非法的间接寻址
// 在通过万能指针修改变量的值时,需要找到变量对应的指针类型
*(int*)p = 100;
printf_s("%d\n", a);
int a = 100;
int b = 200;
// 常量指针
// 修饰 *,指针指向内存区域不能修改,指针指向可以改变
const int* p1 = &a;
// *p1 = 2;
p1 = &b;
// 指针常量
// 修饰 p2,指针指向不能改变,指针指向的内存可以修改
int* const p2 = &a;
// p2 = &b;
*p2 = 2;
指针操作数组
数组名字是数组的首元素地址,但它是一个常量
int arr[] = {1, 2, 3};
// arr = 10; // 其报错,因为a相当于一个指针常量,其指向不能改变
// 数组名是数组第一个元素的首地址
int* p = arr; // p 和 arr 等价,指向数组的指针,数组指针
for (int i = 0; i < 3; ++i) {
printf_s("%d\n", *p++); // 使用指针偏移
// printf_s("%d\n", *(p+i)); // 相当于索引取值
// printf_s("%d\n", p[i]); // 索引取值
}
int step = p - arr; // 两指针相减,得到的是指针偏移的步长
printf_s("%d\", step);
数组作为函数参数会退化为指针,丢失了数组的精度
指针操作数组时,下标允许是负数
int*,+1的结果是增加一个int的大小char*,+1的结果是增加一个char的大小#include <stdio.h>
void my_strcpy1(char* dest, char* src) {
int i = 0;
while (*(src+i)) {
*(dest+i) = *(src+i);
i++;
}
*(dest+i) = 0;
}
void my_strcpy2(char* dest, char* src) {
// 纯指针偏移
while (*src) {
*dest++ = *src++;
}
*dest = 0;
}
void my_strcpy3(char* dest, char* src) {
// 纯指针偏移
while (*dest++ = *src++);
}
int main() {
char str_[] = "hello world";
char dest[100];
my_strcpy3(dest, str_);
printf_s("%s\n", dest);
return 0;
}
两个指针进行运算会变成野指针,其为没有意义的操作(两数组指针相减,其为偏移量);但是可以进行比较运算
指针数组,它是数组,数组的每个元素都是指针类型
int a = 1;
int b = 2;
int c = 3;
int* d = &a;
int* e = &b;
int* f = &c;
int* arr[] = {d, e, f}; // 指针数组,存储指针的数组
指针数组里面也可以存储数组,指针数组是一个特殊的二维数组模型
#include <stdio.h>
int main() {
int a[] = {1, 2, 3};
int b[] = {4, 5, 6};
int c[] = {7, 8, 9};
int* d[] = {&a, &b, &c}; // int** d = int* d[];
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
// 访问指针数组里面的内容
// printf_s("%d\t", d[i][j]);
// printf_s("%d\t", *(d[i] + j));
// printf_s("%d\t", *(*(d+i) + j));
}
puts("");
}
return 0;
}
二级指针相当于指针数组
二级指针加偏移量相当于跳过了一维数组的大小
#include <stdio.h>
int main() {
int a[] = {1, 2, 3};
int b[] = {4, 5, 6};
int c[] = {7, 8, 9};
int* d[] = {&a, &b, &c};
int** p = d;
// 二级指针加偏移量,相当于跳过了一个一维数组
printf_s("%d", **(p + 1));
// 一级指针加偏移量,相当于跳过了一个元素
printf_s("%d", *(*p + 1));
return 0;
}
#include <stdio.h>
void test(int* a) {
*a = 20;
}
int main() {
int a = 10;
printf_s("调用函数前:%d\n", a);
test(&a);
printf_s("调用函数后:%d\n", a);
return 0;
}
数组名做函数参数,函数的形参会退化成指针
#include <stdio.h>
#ifndef bool
typedef int bool;
#define true 1
#define false 0
#endif
void bubble(int* arr, int len){
// 升序排序
for (int i = 0; i < len; ++i) {
for (int j = 0; j < len - i - 1; ++j) {
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int a[] = {4, 1, 5, 9, 6, 8, 7};
bubble(a, 7);
for (int i = 0; i < 7; ++i) {
printf_s("%d\t", *(a + i));
}
puts("");
return 0;
}
指针作为函数返回值
#include <stdio.h>
#ifndef bool
typedef int bool;
#define true 1
#define false 0
#endif
char* my_strchr(char* str_, char ch) {
while (*str_) {
if (*str_ == ch) {
return str_; // 返回切片后的字符数组
}
*str_++;
}
return NULL; // 返回没有找到要开始切片的元素
}
int main() {
char* str_ = "hello world";
char* b = my_strchr(str_, 'l');
printf_s("%s\n", b);
return 0;
}
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
函数指针不可以进行运算
#include <stdio.h>
#ifndef bool
typedef int bool;
#define true 1
#define false 0
#endif
void (*pointer) (); // 定义一个函数指针
void func() {
printf_s("hello");
}
void print_hello(void (*p)()) {
p();
}
int main() {
pointer = func; // 将函数的地址赋值给函数指针
pointer(); // 调用函数
// 也可以将函数指针作为参数传递给函数
print_hello(func);
/*
等价于
void (*p)(); // 定义函数指针
p = func; // 给函数指针赋值
p(); // 调用函数指针
*/
return 0;
}
函数指针一般用于传递回调函数上面
使用strstr方法,来统计出现的次数
#include <stdio.h>
#include <string.h>
int main() {
char ch[] = "hello world";
char a[] = "l";
char* p = strstr(ch, a); // 返回第一次出现字符串的地址
int count = 0;
while (p) { // 如果字符串不为空
count++;
p += strlen(a); // 跳过要查找的字符串
p = strstr(p, a); // 再次截取字符串
}
printf_s("%d\n", count);
return 0;
}
char* strcpy(char* dest, char* src);
dest:目标字符串src:源字符串char* strncpy(char* dest, char* src, size_t n);
int strcmp(char* s1, char* s2);
s1:字符串1s2:字符串2s1 = s2:返回值等于0s1 > s2:返回值大于0s1 < s2:返回值小于0int strncmp(char* s1, char* s2, size_t n);
char* strcat(char* dest, char* src);
\0也会追加过去dest:目标字符串的首地址src:源字符串的首地址char* strncat(char* dest, char* src, size_t n);
\0也会追加过去int sprintf(char* str, const* format, ...);
\0为止str:字符串首地址format:字符串格式化,用法和printf()一样char* strchr(const char* s, char c);
s:字符串首地址c:字符char* strstr(const char* dest, const char* src);
dest:原字符串的首地址src:匹配字符串的首地址char* strtok_s(char* str, const char* delim, char** context);
str:要分割的字符串delim:分割符context:一个字符串数组的第一个元素的首地址int atoi(const char* nptr);
atoi()会扫描nptr,跳过前面的空格字符,知道遇到数字或正负号才开始转换,而遇到非数字或字符串结束符,才会结束转换,并将结果返回nptr:待转换的字符串atof()/atol()#include <stdlib.h>里面C语言变量的作用域分别为:
局部变量也叫auto自动变量,auto可不写,一般情况下代码块内部定义的变量都是自动变量,它有如下特点:
extern声明,extern int a;这里是声明,而不是定义静态局部变量
static局部变量的作用域也是在定义的函数内有效static局部变量的生命周期和程序运行周期一样,同时static局部变量的值只初始化一次,但可以多次赋值static局部变量若未赋以初值,则由系统自动赋值,数值型变量自动赋初值0,字符串变量赋空字符静态全局变量
在C语言中,函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数也没法使用
对于不同文件中的static函数名字可以相同
注意:
static函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的C 代码经过预处理、编译、汇编、链接,4步后生成一个可执行程序
代码区:
数据区:
栈区:
堆区:
malloc colloc reallocfree()程序在加载到内存前,代码区和全局区的大小就是固定的,程序运行期间不能改变。然后,执行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区、数据区和未初始化数据区之外,还额外增加了栈区、堆区
#include <stdio.h>
#include <stdlib.h>
int main() {
// 开辟堆空间存储数据
int* p = (int *)malloc(sizeof(int)*1000); // 开辟1000个整型数据的空间
// 使用堆空间
*p = 123;
printf_s("%d", *p);
// 释放堆空间
free(p);
// 将指针置空,防止出现野指针
p = NULL;
return 0;
}
注意:
释放指针要释放同一个指针
#include <stdlib.h>
int main() {
int* p = (int *)malloc(sizeof(int)*1000);
p = 123; // 改变了指针的地址,同时指针偏移也会改变指针的地址,其为无主指针
free(p); // 释放指针会报错
return 0;
}
#include <string.h>
void* memset(void* s, int c, size_t n);
s:需要操作内存s的首地址c:填充的字符,unsigned char—— 0 ~ 255n:指定需要设置的大小,单位是字节void* memcpy(void* dest, void* src, size_t n);
dest:目标内存首地址src:原内存首地址,注意:首地址不可以重叠n:需要拷贝的字节数dest的首地址memmove()功能用法和memcpy()一样,区别在于:当内存空间重叠时,memmove()仍然能处理,不过执行效率更低
void* memcmp(const void* s1, const void* s2, size_t n);
s1:内存首地址s1s2:内存首地址s2n:需比较的前n个字节s1 = s2:返回值等于0s1 > s2:返回值大于0s1 < s2:返回值小于0