当前位置: 首页 > news >正文

C语言笔记

1.数据的类型和输入输出

数据类型-常量-变量(整型-浮点-字符)

  1. 数据类型:

  1. 常量

值不能改变

分为整型(int),浮点型(float),字符型(char),字符串型(char)

数据类型分类定义/表示方式占用空间示例注意事项
浮点型数据浮点型常量- 小数形式:直接写小数 - 指数形式:aeb(a×10^b)0.1253e-3(即 0.003)<font style="color:#DF2A3F;">e</font><font style="color:#DF2A3F;">E</font> 前必须有数字,后面指数必须是整数。 正确示例:1e31.8e-3-123e-6-1e-3 错误示例:e32.1e3.5.e3e
浮点型变量使用 float 定义4 字节float f = 3.14;float 精度有限,如需更高精度可用 double
字符型数据字符型常量单引号括起来的单个字符1 字节'a''A''1'' '错误示例:'abc'"a"" "(多字符或字符串)
转义字符\ 开头的特殊字符1 字节\n 换行、\b 退格、\\ 反斜杠常用于控制输出格式
字符型变量使用 char 定义1 字节char c = 'x';实际存放的是字符的 ASCII码

整型之符号常量

#include <stdio.h>
#define PI 3+2
int main(){int i=PI*2; printf("i=%d\n",i);  //printf用于输出printf("i size = %d\n",sizeof(i));  //sizeof可以用来计算某个变量的空间大小return 0;
}输出:
7

因为符号变量PI是直接替换的效果,因此会变成int i = 3+2*2

字符型数据之字符型常量

用单引号括起来的一个字符是字符型常量,且只能包含一个字符,如’a’,‘A’

一个字符常量存到一个字符型变量中,实际上并不是把该字符的字型存到内存中,而是把该字符的ASCII码值放到存储单元中

大写字母A是65,小写字母a是97

    // 大写变小写char c='A';printf("%c\n",c+32);  //以字符形式输出,aprintf("%d\n",c);  //以数值形式输出,65

字符型数据之字符串型常量

由一对双引号括起来

以字符’\0’作为字符串结束标志

  1. 变量

变量代表内存中具有特定属性的一个存储单元,值可以改变

由编译系统为每个变量名分配对应的内存地址(就是空间)

变量命名:C语言规定标识符只能由字母,数字,下划线组成,且第一个字符必须为字母或下划线

区分大小写

变量名不能与关键字同名

混合运算

  1. 强制类型转换
#include <stdio.h>
int main(){int i=5;float j=i/2;float k=(float)i/2; //这里做的是整型运算,因为左右操作数都是整型printf("%f\n",j);printf("%f\n",k);return 0;
}输出:
2.000000
2.500000

在 C 语言中,类型转换遵循“由低精度向高精度”原则:

  • 所有 charshort 在表达式计算时会先自动提升为 int
  • 之后按精度从低到高依次是:int → long → float → double → long double
  • 在混合运算时,类型会自动向精度更高的方向转换;
  • 如果使用强制类型转换 (type),则表达式结果会临时变为指定类型,可能发生截断或精度丢失。

** char/short → int → long → float → double → long double **

  1. printf

printf函数将这些类型的数据格式化为字符串后,放入标准输出缓冲区,然后将结果显示到屏幕上

I/O即input/output

格式化输出:

  • 格式化输出的基本概念

printf 可以在输出字符串时,把变量的值插入到字符串里。
格式:

printf("格式控制字符串", 参数1, 参数2, ...);

格式控制字符串:普通文字 + 特殊的格式符号(占位符)。

参数:依次替换这些占位符,按顺序匹配

  • 示例讲解
int age = 21;
printf("Hello %s, you are %d years old\n", "Bob", age);

"Hello %s, you are %d years old\n" 是格式控制字符串

- `%s` → 输出字符串 `"Bob"`
- `%d` → 输出整数 `21`
- `\n` → 换行

最终输出:

Hello Bob, you are 21 years old
  • 常用格式控制符(图里表格)
占位符含义示例
%c输出单个字符'A' → A
%d输出带符号十进制整数-123 → -123
%f输出浮点数(默认 6 位小数)3.14 → 3.140000
%s输出字符串"Hello" → Hello
%u输出无符号十进制整数3000000000U → 3000000000
%x输出无符号十六进制整数(小写字母)255 → ff
%X输出无符号十六进制整数(大写字母)255 → FF
%p输出指针(地址)0x7ffeefbff5c0
  • 常见扩展用法
  1. 限制小数位数
printf("%.2f", 3.14159);  // 输出 3.14
  1. 设置输出宽度
printf("%5d", 42);   // 输出 "   42"(右对齐,占5位)
printf("%-5d", 42);  // 输出 "42   "(左对齐)
  1. 组合
printf("%8.3f", 3.14159);  // 占8位宽,小数保留3位 → "   3.142"
int i=10;
float f=96.3;
printf("student number=%3d score=%5.2f\n",i,f);
printf("student number=%-3d score=%5.2f\n",i,f);int age=19;
printf("hello %s ,you are %d years old\n","zxy",age);	
输出:
student number= 10 score=96.30
student number=10  score=96.30
hello zxy ,you are 19 years old

整形进制转换

一字节(byte)为8位(bit),1位即二进制的1位,它存储0或1

1K=1024字节

1Mb=1024KB

1GB=1024MB

CPU采取了小端方式进行数据存储,即低位在前,高位在后

十进制 0-9

八进制 0-7

十六进制 0-9 a-f

二进制 0000 0001

  1. 给定的二进制数
0100 1100 0011 0001 0101 0110 1111 1110

这是一个 32 位的二进制数(前面可能省略了 0)。

  • 最低位(最右边)是 2 的 0 次方;
  • 最高位(最左边)是 2 的 31 次方;
  • 若最高位为 1,则代表负数(补码形式)。这里最高位是 0,说明是正数。
  1. 转换成 八进制

规则:二进制每 3 位分一组,转换为 1 位八进制。

步骤:

  • 将上面的二进制从右到左,每 3 位分组:
010 011 000 011 000 101 011 011 111 110
  • 每 3 位换算成八进制 0~7 的数:
010 = 2, 011 = 3, 000 = 0, 011 = 3, 000 = 0, 101 = 5, 011 = 3, 011 = 3, 111 = 7, 110 = 6
  • 得到八进制数:
011414253376

(前面加 0,是为了保证完整性。)

  1. 转换成 十六进制

规则:二进制每 4 位分一组,转换为 1 位十六进制。

步骤:

  • 将二进制分组:
0100 1100 0011 0001 0101 0110 1111 1110
  • 每 4 位换算:
0100 = 4
1100 = C
0011 = 3
0001 = 1
0101 = 5
0110 = 6
1111 = F
1110 = E
  • 得到十六进制数:
0x4C3156FE
  1. 转换成 十进制

规则:按权展开,每位上的二进制数 × 对应的 2 的幂次。

即:

010011000011000101011011111110

经过计算,结果为:

1278301950
#include <stdio.h>
int main(){int i=123;printf("%d\n",i);  //十进制输出printf("%o\n",i); //八进制printf("%x\n",i); //十六进制return 0;
}输出:
123
173
7b

scanf读取标准输入

  1. scanf输入规则

scanf用来读取标准输入,scanf把标准输入内的内容,需要放到某个变量空间里,因此变量必须取地址&

scanf会阻塞,是因为输入缓冲区是空的

scanf("%d",&i);
printf("i=%d\n",i);char c;
scanf("%c\n",&c);   // <- 这里出问题
printf("%c\n",c);输出:
12
i=12
1

现象复现(两种常见输入)

  • 情形A:你在一行里输入 123 a 然后回车
    1. %d 读走 123,停止在空格;缓冲区还剩 " a\n"
    2. %c 直接读下一个字符——那就是空格 ' 'c=' 'printf 打印空格,你几乎看不见
    3. 格式串中的 \n(见下文)会继续吞掉后面的空白(含回车)并等待下一个非空白;所以程序像“卡住”,直到你再键入一个非空白字符
  • 情形B:你在两行输入:第一行 123 回车;第二行 a 回车
    1. %d 读走 123;第一行的回车 \n 留在缓冲区
    2. %c 立刻把这个残留的 \n 当字符读进 c;打印看起来像“啥都没输出”
    3. 末尾的 \n 又会等待下一个非空白,导致继续“等输入”

小结:要么 c 读到了空格/回车(你看不见),要么被**末尾的 **\n 搞得阻塞。

根本原理:scanf 的三条关键规则

  1. 数字类转换(%d、%f…)会跳过前导空白
    它先丢掉空白,再从第一个数字开始读,遇到第一个不匹配的字符就停下,那个不匹配字符会留在输入缓冲区
  2. %c%[...]%n** 不会跳过空白**
    %c 是“原样读一个字节”,下一个是什么就读什么(可能是空格/回车)。
  3. 格式串里的“空白”含义(包括空格和 \n
    scanf 的格式串中,任意空白字符(空格、\t\n)都表示:跳过任意数量(0 个或更多)的空白输入
    • 若空白在中间:只是帮你把接下来的空白都吃掉
    • 若空白在格式串末尾(如你写的 "%c\n"):它会一直吞空白,并等待直到看到一个非空白字符才算这次调用结束,于是看起来像卡住
    • 这也是为什么把 \n 放在末尾很容易出问题的原因

为什么看起来“没输出 c”

  • 第一:%d 后的换行(或空格)常常被 %c 读走c 其实是空白字符,肉眼看不到
  • 第二:"%c\n" 的末尾 \n 会继续吃空白并等待非空白,导致阻塞,你误以为没打印

正确写法(任选其一)

%c 前加一个空格(最简单、最推荐)

scanf(" %c", &c);   // 注意前面的空格

含义:先把所有空白(空格/回车/Tab)都跳过,再读一个真实字符给 c

手动清空到行尾(在读 %c 前清掉残留回车)

scanf("%d",&i);
int ch; while ((ch = getchar()) != '\n' && ch != EOF) { }  // 清掉本行剩余的字符(含回车)
scanf("%c",&c);

先读整行,再解析(更可控,适合复杂输入)

char line[128];
fgets(line, sizeof(line), stdin);               // 把一行读进来
sscanf(line, "%d", &i);fgets(line, sizeof(line), stdin);               // 再读下一行
sscanf(line, " %c", &c);                        // 这里仍建议在 %c 前放空格

常见误区

  • 不要scanf 里把 \n 放在末尾(如 "%c\n"):极易导致“等待非空白”而阻塞
  • 不要fflush(stdin) 清输入:这是未定义行为(MSVC 的扩展除外),跨平台不可靠
  • 记住:%c 不会跳过空白;想跳过就在格式串里显式写一个空格" %c"

“数字转换会跳过前导空白,%c 不会;要跳过,就写 " %c";不要在 scanf 的末尾写 \n。”

  1. 多种类型混合输入

#include <stdio.h>
int main(){int i,ret;float f;char c;ret=scanf("%d %c%f",&i,&c,&f);printf("i=%d,c=%c,f=%5.2f\n",i,c,f);
}输出:
1 c 12.8
i=1,c=c,f=12.80

OJ网站的使用

oj是online judge的缩写,也就是在线判题,以下为链接

OnlineJudge

2.运算符与表达式

算术运算符与关系运算符

  1. 算术运算符

#include <stdio.h>
int main(){int result=4+5*2-6/3+10%4;printf("result=%d\n",result);
}输出:
result=14
  1. 关系运算符

#include <stdio.h>
int main(){int a;while (scanf("%d",&a)){if (a>3&&a<10)     {printf("a在3和10中间\n");}else{printf("a不在3和10中间\n");}}
}

逻辑运算符与赋值运算符,求字节运算符

逻辑运算符

#include <stdio.h>
int main(){int i=0,j=1;while (scanf("%d",&i)){if (i%4==0 && i%100!=0 || i%400==0){printf("i is leap year\n");}else{printf("i is not leap year\n");}}i=!!j;printf("i value=%d\n",i);
}输出:
m
i value=1

短路运算:

当i为假时,不会执行逻辑与后面的表达式,称为短路运算

#include <stdio.h>
int main(){// 逻辑与和逻辑或的运算int i=1;i&&printf("you can see me\n");// 前面的值为真,则日志不运行int j=0;j||printf("you can see me\n");
}输出:
you can see me
you can see me
  1. 逻辑与 && 的短路规则
  • 表达式:expr1 && expr2
  • 计算顺序:
    1. 先算 expr1
    2. 如果 expr1假(0),那结果一定是 ,不会再算 expr2(直接“短路”)。
    3. 如果 expr1真(非0),才会去算 expr2,结果取决于 expr2

所以:

  • 0 && anything → 直接是 0,不计算 anything
  • 1 && anything → 结果等于 anything 的真假。
  1. 逻辑或 || 的短路规则
  • 表达式:expr1 || expr2
  • 计算顺序:
    1. 先算 expr1
    2. 如果 expr1真(非0),结果一定是真,expr2 根本不会计算。
    3. 如果 expr1假(0),才会算 expr2

所以:

  • 1 || anything → 直接是 1,不计算 anything
  • 0 || anything → 结果等于 anything 的真假。
  1. 总结

短路运算的意义:

  • 提高效率(不用多算)。
  • 避免错误(常用于防止非法操作)。
    例如:
if (p != NULL && p->value == 10) { ... }

如果 p == NULL,第一个条件为假,第二个 p->value 不会执行,就不会导致程序崩溃。

赋值运算符

也就是"="

等号左边放变量

符合赋值运算符:+=,-=,*=,/=

求字节运算符-----sizeof

用于求常量或变量所占用的空间大小

int i=0;
printf("i size is %d\n",sizeof(i));    输出:
i size is 4

算术运算符优先级高于关系运算符,关系运算符优先级高于逻辑与和逻辑或运算符

  • 单目运算符:只需要一个操作数,比如 !a, -a, ++a, --a
  • 双目运算符:需要两个操作数,比如 a + b, a && b
  • 三目运算符:需要三个操作数,比如 a > b ? a : b

3.选择+循环

选择if-else

5>3&&8<1-!0

if ----else if----else循环

#include <stdio.h>int main() {int number;        // 输入的数量double cost;       // 单价double total;      // 总价// 输入部分printf("请输入购买数量:");scanf("%d", &number);// 判断逻辑if (number > 500) {cost = 0.15;} else if (number > 300) {cost = 0.10;} else if (number > 100) {cost = 0.075;} else if (number > 50) {cost = 0.05;} else {cost = 0.0;}// 计算总价total = number * cost;// 输出部分printf("购买数量: %d\n", number);printf("总价: %.3f 元\n", total);return 0;
}

可嵌套:

循环while,for,continue,break

  1. while循环

先判断表达式,后执行语句

#include <stdio.h>
// 计算1到100的和
int main(){int i=1,total=0;while (i<=100){total+=i;i++;}printf("total=%d\n",total);
}

辨析:

写法表达式返回值自增发生时间
i++自增前的值先返回,再加 1
++i自增后的值先加 1,再返回
  1. for循环

#include <stdio.h>
int main(){int i,total=0;for (int i = 0; i <= 100; i++){total+=i;}printf("total=%d\n",total);
}
  1. continue语句

跳过本次循环

#include <stdio.h>
// 求1-100偶数和
int main(){int i,total;total=0;for (int i = 0; i <= 100; i++){if (i%2==0){continue;}total+=i;}printf("total=%d\n",total);
}

while循环慎用continue,小心死循环

  1. break语句

#include <stdio.h>
int main(){int i,total;for (int i = 0; i <= 100; i++){if (total>2000){break;}total+=i;}printf("total=%d\n",total);
}
  1. 循环嵌套

最常见的是for循环嵌套for循环

#include <stdio.h>int main() {int i, j;// 外层循环控制行for (i = 1; i <= 9; i++) {// 内层循环控制列for (j = 1; j <= i; j++) {printf("%d×%d=%-3d  ", j, i, i * j);  // %-3d 表示结果左对齐,占 3 个宽度,保证整齐}printf("\n");  // 换行}return 0;
}

打印这个图:

#include <stdio.h>int main() {int i, j;// 控制行数(5 行)for (i = 1; i <= 5; i++) {// 每一行打印 i 个 *for (j = 1; j <= i; j++) {printf("*");}printf("\n"); // 换行}return 0;
}

4.一维数组和字符数组

一维数组

数组,是指一组具有相同数据类型的数据的有序集合

int a[10]={0};  //所有元素值为0

一维数组int mark[100],在内存中的存放情况如图所示,都是整型元素,占用4字节,数组的引用方式是"数组名[下标]",所以访问数组mark中的元素是mark[0],mark[1]…

初始化方法:

int a[10]={0,1,2,3,4,5,6,7,8,9};
int a[10]={0,1,2,3,4}; //只给前五个元素赋值,后五个元素值为0

数组访问越界与数组的传递

  1. 数组越界
#include <stdio.h>
int main(){int a[5]={1,2,3,4,5};int j=20;int i=10;a[5]=6;  //越界访问a[6]=7;  //越界访问会造成数据异常printf("i=%d\n",i);
}输出:
i=7
  • C 语言本身不检查边界,所以编译器不会报错。
  • 运行时也不会报错(不像 Java、Python 那样会抛异常)。
  • 如果越界访问的地址还在当前进程能访问的内存范围内,就不会崩溃,但数据可能被篡改。
  • 如果越界到非法内存地址,才会出现段错误 (Segmentation fault)。

因为 a[5]a[6] 已经超出数组定义的范围,它们写到的地址就有可能覆盖 ij 的内存,也可能覆盖别的内容。这就是未定义行为

程序可能看起来「正常运行」,也可能「数据错乱」,甚至「直接崩溃」

  1. 数组的传递

经典的错误:

void print(int a[5]) {for (int i = 0; i < sizeof(a)/sizeof(int); i++) {printf("%d\n", a[i]);}
}

看似在遍历 5 个元素,其实只会遍历 1 或 2 个,输出不完整。

看起来 a[5] 是长度为 5 的数组,但在函数参数里,数组会退化(decay)成指针,即 int *a

void f(int a[5]) {sizeof(a);  // 实际是 sizeof(int*),因为 a 是指针
}

C 语言规定:在大多数表达式中,数组名会自动转换为指向其首元素的指针
所以:

  • sizeof(a) 得到的是 指针的大小(在 32 位系统是 4 字节,在 64 位系统是 8 字节),而不是整个数组的大小。
  • sizeof(int) 一般是 4。
  • 所以 sizeof(a)/sizeof(int) 在 64 位系统下是 8/4 = 2,循环只会跑 2 次。
    在 32 位系统下是 4/4 = 1,只会跑 1 次。

这就导致实际输出的个数不对。

正确写法:

#include <stdio.h>
// 子函数把某一个常用功能,封装起来的作用
// 数组名传递到子函数后,子函数的形参接收到的就是数组的起始地址,因此不能把数组的长度传递给子函数
void print(int a[], int length){for (int i = 0; i < length; i++){printf("%d\n",a[i]);}
}int main(){int a[5] = {1,2,3,4,5};// 想知道数组的长度,必须自己传进去print(a, 5);return 0;
}

字符数组与scanf读取字符串

  1. 字符数组

#include <stdio.h>
int main(){char c[3]={'I','a','d'};  //没有结束符,输出会乱码char  b[4]="Iad";printf("%s",b);
}

输出字符串乱码时,要去看字符串数组中是否存储了结束符’\0’

字符数组初始化和传递

#include <stdio.h>
// 模拟printf("%s",c)操作
void print(char d[]){int i=0;while (d[i]) //当走到结束符时,循环结束{printf("%c",d[i]);i++;}printf("\n");
}int main(){char c[6]="hello";char d[5]="how";printf("%s\n",c);print(d);
}
  1. scanf读取字符串
#include <stdio.h>
int main(){char c[10];char d[10];// scanf会自动往字符数组中放结束符scanf("%s",c);  //字符数组名c中存储了数组的起始地址,故无需加&printf("%s\n",c);scanf("%s%s",c,d);printf("c=%s,d=%s\n",c,d);
}

scanf在使用%s读取字符串时,还会忽略空格和回车,上述程序

输入:how are

输出:how

那如果想一次性读取到how are怎么办?看下一节

fgets与puts讲解,strlen-strcmp-st

1. gets 函数

gets 用于从标准输入读取一行字符串。它的原型如下:

char *gets(char *str);
  • 功能:**gets**** 从标准输入读取一行字符,直到遇到换行符(**\n**)或者文件结束符(EOF)**。然后将该字符串存储在传入的字符数组中,换行符会被替换为字符串的终止符(\0)。
  • 缺点:gets 不会检查输入字符串的长度,因此如果输入的字符串超出了预分配的内存空间,可能会导致缓冲区溢出,这是非常危险的,会引发程序崩溃或安全漏洞。
char str[100];
gets(str);  // 从用户输入读取字符串

建议:由于 <font style="color:#DF2A3F;">gets</font> 存在严重的安全隐患(缓冲区溢出),它已经被 C11 标准废弃,应该避免使用。可以使用 <font style="color:#DF2A3F;">fgets</font> 来代替。

2. puts 函数

puts 用于向标准输出打印一个字符串,并自动在字符串末尾加上换行符。它的原型如下:

int puts(const char *str);
等价于:
printf("%s\n",c);
  • 功能:puts 打印传入的字符串,并在末尾自动添加一个换行符,表示输出结束。
  • 返回值:如果输出成功,puts 返回一个非负值;如果发生错误,返回 EOF
puts("Hello, World!");  // 输出:Hello, World!

fgets 函数

char *fgets(char *str, int n, FILE *stream);

参数:

  • str:指向存储读取内容的字符数组的指针。读取的数据将存储在这个数组中。
  • n:指定最大读取字符数(包括末尾的 \0),即最多读取 n-1 个字符。确保数组空间不会溢出。
  • stream:指定输入流,通常是 stdin(标准输入),但也可以是其他文件流。

返回值:

  • 成功时:返回 str(指向字符数组的指针),即返回读取到的字符串。
  • 失败时:返回 NULL,通常是由于读取错误或遇到文件结束(EOF)。
  1. 避免缓冲区溢出:<font style="color:#DF2A3F;">fgets</font> 读取字符时会保证最多读取 <font style="color:#DF2A3F;">n-1</font> 个字符,且自动在末尾添加字符串终止符’\0’。这避免了像 <font style="color:#DF2A3F;">gets</font> 函数那样引起的溢出问题。
  2. 读取换行符:如果用户在输入时按下回车(即换行符),fgets 会将换行符(\n)包括在字符串中,除非读取的字符数达到了 n-1,此时换行符会被丢弃。
  3. 可以读取空行:与 scanf 不同,fgets 允许读取空行,即用户直接按回车时,返回一个仅包含换行符和终止符的字符串。
#include <stdio.h>int main() {char str[100];// 使用 fgets 读取一行输入,最多读取 99 个字符,保留 1 个字符位置给 '\0'printf("请输入一行文字: ");if (fgets(str, sizeof(str), stdin) != NULL) {printf("你输入的是: %s\n", str);} else {printf("读取输入失败!\n");}return 0;
}
请输入一行文字: Hello, World!
你输入的是: Hello, World!

需要注意的几点:

  1. 换行符的处理:
// 去掉字符串末尾的换行符
str[strcspn(str, "\n")] = '\0';
- 如果用户输入了换行符,`fgets` 会将其保留在字符串中。如果不希望字符串包含换行符,可以在处理字符串后去掉它。
  1. 最大字符数:
    • fgets 读取的字符数最多是 n-1,即数组的大小减 1,这样才能为字符串的结束符 \0 留出空间。
  2. 文件结束:
    • 如果 fgets 读取到文件的结束标志(EOF),它将返回 NULL,表示没有更多数据可供读取。

str系列

strcmp 的规则复习

  • strcmp(a,b) 会逐字符比较:
    • 如果第一个不相等的字符 a[i] < b[i],返回 负数
    • 如果 a[i] > b[i],返回 正数
    • 如果完全相同,返回 0
#include <stdio.h>
#include <string.h>
// 原理也就是遍历
int mystrlen(char c[]){int i=0;while (c[i]){i++;}return i;
}int main(){int  len;  //用于存储字符串长度char c[20];char d[100]="world";char e[200];gets(c);puts(c);len=strlen(c);printf("len=%d\n",len);len=mystrlen(c);printf("my len=%d\n",len);strcat(c,d);  //把d中的字符串拼接到c中puts(c);strcpy(d,c);  //把c中的字符串复制给dputs(d);strcpy(e,c);puts(e);//用ASCII码去比较。c和e相等输出0printf("c?d=%d\n",strcmp(c,e));// c大于"how",返回是正值,反之返回负值puts(c);printf("c?d=%d\n",strcmp(c,"tow"));return 0;
}输入:a
输出:
a
len=1
my len=1
aworld
aworld
aworld
c?d=0
aworld
c?d=-1

最后一个值是 -1 的原因是 **"a"** 的 ASCII 码比 **"t"** 小,strcmp 返回负数,刚好实现返回了 -1。

5.指针

指针的本质(间接访问原理)

通俗的讲,指针就是地址,*是取值,&是取址

数组名是首地址,arr[i]等于*(arr+i)

我们常说的指针其实是指针变量,也就是存储地址的变量

#include <stdio.h>
int  main(){int i=5;char c='a';int *i_pointer=&i;  //定义指针变量i_pointer,并初始化为i的地址char *c_pointer;c_pointer=&c;*i_pointer=10;  //间接访问---通过指针修改 iprintf("i=%d\n",i); //直接访问----直接使用 i
}

直接访问 就是通过 变量名 来访问内存里的值。

间接访问 就是通过 指针(保存的地址)去访问/修改变量。

指针变量大小:

注意事项:

指针的传递使用场景

指针使用场景分为传递和偏移

C语言的函数参数传递,永远是值传递
但如果传递的是“指针变量”,那么它拷贝的是地址值。

#include <stdio.h>void change(int *x) {  //等价于x=&a*x = 20;   // 通过间接访问拿到了变量a的地址,修改x指向的变量
}int main() {int a = 10;change(&a);   // 把a的地址传进去,实参是&aprintf("%d\n", a); // 输出20return 0;
}
  • &a 把 a 的地址传递给函数 change
  • change 内部 *x = 20,相当于直接修改了 a

指针的偏移使用场景

  1. 偏移

指针可以进行加减运算,单位不是字节,而是指向类型的大小

单位是“类型大小”,p+1表示跳到下一个元素

int arr[5] = {10, 20, 30, 40, 50};  //数组名内存储了数组的起始地址,arr中存储的就是一个地址值
int *p = arr; // 定义指针变量p,p指向arr[0]printf("%d\n", *p);     // 10
printf("%d\n", *(p+1)); // 20
printf("%d\n", *(p+2)); // 30// 让p直接指向数组末尾
p=&arr[4];
printf("%d\n",*p);
  • 偏移的长度是其基类型的长度,也就是偏移sizeof(int),这样通过*(p+1)就可以得到元素a[1]。<font style="color:#DF2A3F;">p+1</font> 不是地址+1字节,而是地址+<font style="color:#DF2A3F;">sizeof(int)</font>字节
    如果 int 是4字节,那么 p+1 = 原地址+4
  • 同理,p+2 = 原地址+8

数组取下标的操作正是通过指针偏移实现的

  1. 指针与一维数组

#include <stdio.h>
void change(char *d){*d='H';d[1]='E';*(d+2)='L';
}
int main(){char c[10]="hello";change(c);puts(c);return 0;
}

数组名作为实参传递给子函数时,是弱化为指针的

指针与malloc动态内存申请,栈与堆的差异

C语言的数组长度固定是因为其定义的整型,浮点型,字符型变量都在栈空间中,而栈空间的大小在编译时是确定的。如果使用的空间大小不确定,那么就要使用堆空间

// 动态内存申请----malloc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){int i;  //代表我们要申请多大字节的空间char *p;  //void*类型的指针不能偏移的,因此不会定义无类型指针scanf("%d",&i);  //输入要申请的空间大小// malloc返回的void*代表无类型指针,要强转成charp=(char*)malloc(i); //使用malloc动态申请堆空间strcpy(p,"malloc success");puts(p);free(p);  //free时必须使用malloc申请时返回的指针值,不能进行任何偏移 printf("free success\n");return 0;
}输入:100
输出:
malloc success
free success

注意!指针本身大小和其指向空间的大小,是两码事

6.函数

函数的声明与定义-嵌套调用

  1. 函数的声明与定义

举个例子:

//func.h
#include <stdio.h>
#include <stdlib.h>
int printstar(int i);  //函数声明
void print_message();//func.c
#include "func.h"
int printstar(int i){  //i为形式参数printf("***********\n");printf("printstar %d\n",i);return i+3;
}
void print_message(){  //可以调用pirntstarprintf("how do you do\n");printstar(3);
}// main.c
// ""是指在当前目录下找头文件
#include "func.h"
int main(){int a=10;a=printstar(a);print_message();printstar(a);return 0;
}

运行:

gcc main.c func.c -o main
.\main.exe

函数声明与定义的差异:

整合成一个

#include <stdio.h>
#include <stdlib.h>// 函数声明
int printstar(int i);
void print_message();// 函数定义
int printstar(int i) {  // i 为形式参数printf("***********\n");printf("printstar %d\n", i);return i + 3;
}void print_message() {  // 可以调用 printstarprintf("how do you do\n");printstar(3);
}// 主函数
int main() {int a = 10;a = printstar(a);   // 调用 printstar(10),返回 13print_message();    // 打印 how do you do,并调用 printstar(3)printstar(a);       // 调用 printstar(13)return 0;
}

  1. 函数的分类与调用

函数的递归调用

函数自身调用自身的操作,称为递归函数。递归函数一定要有结束条件,否则会产生死循环

核心就是找公式:f(n)=n*f(n-1)

5!=5*4!

举个简单的例子:

#include <stdio.h>int f(int n){//一定要有结束条件,一定在return之前if (1==n){return 1;}return n*f(n-1);}
int main(){int n;scanf("%d",&n);printf("f(%d)=%d\n",n,f(n));
}

上台阶问题: 找公式:step(n)=step(n-1)+step(n-2)

总共有 5 级台阶,每次只能上 1 阶或者 2 阶,问有多少种走法。

#include <stdio.h>// 递归函数
int climb(int n) {if (n == 1) return 1;  // 只有一级台阶 → 一种走法if (n == 2) return 2;  // 两级台阶 → 两种走法return climb(n-1) + climb(n-2);  // 递归公式
}int main() {int n = 5;printf("一共有 %d 种走法\n", climb(n));return 0;
}

局部变量与全局变量

#include <stdio.h>
// 全局变量,不建议使用
int i=10;
void pirnt(int a){printf("pirnt i=%d\n",i);
}int main(){{// 局部变量有效范围是离自己最近的花括号int j=5;}printf("main i=%d\n",i);i=5;printf("%d",i);return 0;
}

形参与实参:

局部变量与外部变量

7.结构体以及C++引用讲解

结构体-结构体数组-结构体对齐

  1. 结构体

C语言提供结构体来管理不同类型的数据组合

#include <stdio.h>
struct student
{int num;char name[10];char sex;int age;float score;
};  //结构体类型声明,注意最后一定要加分号
struct student s1,s2;  //定义多个结构体变量int main(){// 初始化struct student s={1001,"zxy",'M',20,98};
}

  1. 结构体数组

#include <stdio.h>
struct student
{int num;char name[10];char sex;int age;float score;
};  //结构体类型声明,注意最后一定要加分号
struct student s1,s2;  //定义多个结构体变量int main(){// 初始化struct student s={1001,"zxy",'M',20,98};struct student sarr[3];   //定义一个结构体数组变量int i;//结构体输出必须单独取访问内部的每个成员s.age=122;printf("%d %s %c %d %f\n",s.num,s.name,s.sex,s.age,s.score);printf("--------------\n");scanf("%d %s %c %d %f",&s.num,s.name,&s.sex,&s.age,&s.score);printf("%d %s %c %d %f\n",s.num,s.name,s.sex,s.age,s.score);  //读取内容后在输出printf("--------------\n");// 把 s 赋值到结构体数组 sarr[0]i=0;sarr[i]=s;// 输出数组中的第一个结构体printf("%d %s %c %d %f\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score);
}

  1. 结构体对齐

#include <stdio.h>
struct student_type1
{double score; //8个字节int height; //4个字节short age;  //2个字节
};  //占用16字节大小的空间
struct student_type2
{int height;char sex;short age;
}; //占用8个字节空间大小// 结构体对齐
int main(){struct student_type1 s1={1,2,3};struct student_type2 s2={7,'m',8};printf("s1 size = %d\n",sizeof(s1)); //16printf("s2 size = %d\n",sizeof(s2)); //8return 0;
}

结构体指针与typedef的使用

  1. 结构体指针的使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 结构体指针
struct student
{int num;char name[10];char sex;
};int main(){struct student s={1001,"zxy",'M'};struct student sarr[3]={1009,"ylp",'M',1002,"qingwei",'M',1003,"ouyang",'F'};struct student *p;  //定义结构体指针变量int num;p=&s;printf("%d %s %c\n",p->num,p->name,p->sex);p=sarr;printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);  //方式一获取成员printf("%d %s %c\n",p->num,p->name,p->sex);  //方式二获取成员printf("------------------\n");p+=1;printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex); // 给结构体指针p通过malloc申请空间(一个结构体大小的空间),并对其成员赋值,再访问p=(struct student*)malloc(sizeof(struct student));p->num=100;p->sex='M';strcpy(p->name,"happy");printf("------------------\n");printf("%d %s %c\n",p->num,p->name,p->sex); return 0;
}

  1. typedef的使用

#include <stdio.h>
// typedef起别名
// 结构体指针
typedef struct student {int num;char name[20];char sex;
} stu, *pstu;// typedef给已有类型起别名
typedef int INTEGER;
int main() {stu s1 = {1001, "Tom", 'M'};  // 用别名 stupstu ps = &s1;                // 用别名 pstuINTEGER num=10;printf("%d\n",num);printf("%d %s %c\n", ps->num, ps->name, ps->sex);return 0;
}

C++引用的讲解

文件后缀:.cpp

#include <stdio.h>
void aaa(int &b){  //形参中写&,要称为引用b=b+1;
}
int main(){int a=10;aaa(a);printf("afer mdify a=%d\n",a);
}
  • 在 C++ 中,int &b 表示 b 是一个引用,它和实参 a 绑定。
  • 所以在函数里对 b 的修改,直接作用到 a
  • 效果相当于 传址调用(call by address),但语法比指针更直观。

等价于

#include <stdio.h>
void aaa(int *b){*b=*b+1;
}
int main(){int a=10;aaa(&a);printf("after modify a=%d\n",a);
}

总结:子函数中修改主函数的变量的值,就用C++的引用

重点!子函数内修改主函数的一级指针变量

#include <stdio.h>// p 是“指针的引用”,q 是普通指针
void modif_pointer(int* &p, int *q){  //引用必须和变量名紧邻p = q;   // 改变 p 的指向
}int main(){int *p = NULL;   // p 初始化为空指针int i = 10;int *q = &i;     // q 指向变量 imodif_pointer(p, q);   // 把 p 修改成和 q 一样printf("after modify_pointer *p=%d\n", *p);return 0;
}
http://www.dtcms.com/a/435074.html

相关文章:

  • 23ICPC合肥站补题
  • LR算法中反向最右推导(Reverse RightMost Derivation)
  • 企业网站托管服务常用指南wordpress ssl证书
  • 专注于响应式网站开发高端定制网站建设高端旅游定制
  • django网站开发教程杭州最便宜的网站建设
  • rpm包的安装方法
  • 内网环境下离线安装软件的完美解决方案(以MySQL为例)
  • 构造函数和初始化列表的关系
  • 济南网站优化建设局网站打不开
  • LabVIEW 系统稳定性计算
  • Rocky Linux 8 安装与配置 TigerVNC 服务完整操作文档
  • Testify Go测试工具包入门教程
  • 南阳网站建设xihewh成都网站建设公司有哪几家
  • **标题:发散创新:探索AR开发框架的核心技术**随着增强现实(AR)技术的飞速发展,AR开发框架成为了开发者们关注的焦
  • 网站推广的优势logo制作免费版
  • 汕头网站建设制作报价网片是干什么用的
  • 江西省住房和城乡建设厅的网站网站设计权限
  • 【人工智能通识专栏】第三十三讲:知识库的构建与应用
  • 、@RequestParam 取出文件项
  • llms.txt:为大模型打造的“网站说明书”
  • 浔川社团再创佳绩
  • wordpress js版本号郑州官网网站优化公司
  • 藏语自然语言处理入门 - 3 找关键词
  • TDengine 时序函数 SAMPLE 用户手册
  • 【动态规划DP:纸币硬币专题】P2840 纸币问题 2
  • wap网站分享到微信屏蔽 wordpress 插件下载
  • 网站com域名上不去cn能网址之家哪个好
  • Python基础入门例程79-NP79 字母转数字
  • 阿里滑块 最新版 分析
  • 独立开发者日常:宝塔面板使用教程