c语言学习笔记(含超详细指针精讲!!)
[1]数据描述与基本操作
这一章节是c语言的入门章节,需要了解数据描述中的常量,变量和数据类型,以及算法问题基本操作中的输入输出,运算符,表达式等知识。
1.1数据类型
从常量和变量的角度
常量
1.整型常量,实型常量,字符型常量,字符串常量
2.符号常量
3.const常量
变量
- 必须先定义后使用
- 变量以标识符为名字
标识符要求:
第一只能包含数字,字母,下标,不能包含其他字符
(下划线在用户标识符里面可以出现多次例如_a_b1)
第二开头不能用数字
第三不能是关键词(关键词是c语言规定的标识符,本质也是标识符,但不能另作他用)
变量名和函数名的要求一样,都不能是关键词
常见关键词

- 从数据类型角度
(1)基本类型(最小单位类型)
- 整型
- 实型
- 字符型
- 结构类型(由基本类型和其他构造类型相结合的复合类型
- 数组类型
- 结构体类型(struct)
- 共用体类型(union)
- 枚举类型(enum)
(3)指针类型
(4)空类型
具体讲解
- 整型常量与整型变量
整型常量:十进制--%d,以非零开始的数eg:101,由正负号和0-9组成
八进制--%o,以0开头的数eg:011,由0-7组成
十六进制--%x,以0x或0X开头的数eg:0x23,由0-9和a-f组成(a-f表示10-15)
(%u表示无符号类型的整型数据)
(整型常量后面加L或I表示长整型数,eg:11L
后面加U或u表示无符号整数,eg:11U)
例题:

整型变量:unsigned不带符号,sign表示带符号(一般省略),二者不影响字节数
Short2 int4
long4 --%ld(无符号%lu)
longlong8 --%lld(无符号%llu)
- 实型常量和实型变量
实型常量:十进制数形式,由小数点和数字组成(必须有小数点)
Eg:1.7 0, .45 都是合法形式
指数形式要求e或E前面必须有数,后面只能为整数,可以带符号,但是符号必须在e后面 补充(指数形式的浮点数必须是十进制的,前缀不能是0和0x)
Eg:10E0.5(错误) E5(错误) 0.5-E5(错误)08e2(错误)
实型变量:float 4 double 8
- 字符型常量和字符型变量
字符型常量:用单引号括起来的单个字符‘a’‘2’‘&’
以‘\’开头的字符序列


字符型变量:char 变量名--%c
因为字符在内存中是用Ascll来储存的‘A’是65,‘a’是97,和整型的储存相似,所以字符数据和整型数据是通用的,既可以用格式符%c也可以用%d表示
字符串常量:用“”表示,以‘\0’作为结束标志。
%s从对应地址的第一个字符开始遍历,直到遇到\0,然后输出字符串(知识点5)
空格特殊用法(知识点6)
1.2输入和输出函数
- 格式输入和输出
- Scanf:scanf的形式是(“格式控制字符串”,地址表列)
这个格式控制字符串是什么呢,它是格式符+非格式字符
格式符:以%开头的,后面有具体数据类型的
非格式字符:字符串里面除格式符以外的其他普通字符,例如,:;
Scanf输入的特殊情况

格式控制字符串的形式是:%[*][最大字符数][长度]类型
*表示跳过当前输入值
长度:%l类型,l表示长整型;%h类型,h表示短整型
- Printf:形式(“格式控制字符串”,输出列表)
格式控制字符串的形式是:%[标志][最小字符数=宽度][.精度][长度]类型
标志:-(左对齐)+ 空格 #


宽度:总说也就是最小字符数,实际数小于宽度就在左边补空格,如果标志是-,那么就在右边补空格

精度:如下

长度:同scanf
1.3运算符与表达式
除了以下表达式还有指针表达式和函数调用表达式
函数调用表达式例如printf(“hello”)注意此时没有分号,也就表达式而不是语句。
Printf输入字符的表达式返回成功输入字符的个数,例如printf(“hello”)返回5,(“a\ns”)返回3;
Printf表达式的返回值是输入字符数,scanf表达式的返回值是成功输入并读取的变量个数。

1.算术运算符和表达式
(1)运算符:+ - * / %
其中取余运算符 %(也就是模运算符)要注意参与运算的数要是整型(或字符型)数据类型
除法运算符/要注意两个整型数据相除结果也是整数,如果至少有一个是实型,则结果也是实型 eg:7/2结果是3;7.0/2结果是3.5
(2)表达式
表达式先看优先性,如果优先性相同就看结合性
算术运算符左结合,/ % *优先于+ -
当有不同数据类型一起进行运算时,要先进行数据类型转换,由低精度转换成高精度,最后转换到同一类型后进行运算
数据类型转换:
- 自动转换
(高)Double ßfloat
Long
Unsighed
(低)Int ßchar ,short
横线箭头是必须转换的
- 强制类型转换
Float a;
(int)a
强制类型转换不改变a的类型,a任然是实型,而整个表达式
(int)a是整型
2.赋值运算符和表达式
(1)运算符:= += -= *= %= /=
Eg:a%=3也就是a=a%3
注意赋值运算符的左边必须是单一变量,不能是常量和表达式
特殊:++ --
运算符的对象只能是简单变量,不能是运算符
前缀还是后缀影响使用时的取值是变化还是不变,单独的i--,i++,不被使用,直接加减1.(右结合)
- 表达式:赋值运算符的表达式是右结合
优先级-- ++高于= += -= *= %= /=
踩雷题:

- 关系运算符和表达式
- 运算符:> >= < <= == !=
- 表达式:关系表达式的值是逻辑值,只有1和0,成立则1,不成立就是0
关系表达式的运算符是左结合
优先级是> >= < <=高于== !=
5>3 1 3>5 0
- 逻辑运算符和逻辑表达式
- 运算符:!&& ||
!1==0 !0==1 5>3(1) || 2<1(0)==1
&&都真则真,||一真则真
注意短路特征
- 表达式:!是右结合,&&和||是左结合
!高于&&高于||
- 逗号运算符和逗号表达式
- 运算符:表达式1,表达式2,表达式3
从左到右,取最后一个值
逗号是分隔符也是逗号运算符,是不是逗号运算符要看逗号是不是在一个单一整体里面,而分隔符则是逗号在多个单一整体里面
Eg:for(i=0;i<n;i++,i++)
表达式三就是逗号运算符,i应该按一次自增2来计算,相当于i+=2
- 位运算
(最最最最最让我困扰的……,但是应该可以解决了
最最最重要的是记住正数的原码=反码=补码,负数的原码要先求绝对值的原码,然后把最高位的符号变成1,反码是符号位不变,数值位取反,补码是反码+1,为什么要求补码呢,因为按位运算就是按照补码来计算的,如果要位运算,必须先搞懂下面这个例子)

- 按位与运算符
&全真才1
- 按位或运算符
|一真则1
- 按位异或运算符
^不同才1,相同则0
- 左移运算符
<<根据右边的数移动位数,高位丢弃,低位补0
- 右移运算符
>>根据右边的数移动位数,低位丢弃,高位看符号情况补,正数补0,负数1
- 按位取反(万能公式:~x = -x-1)
只用记住:补码取反
正数:原码=反码=补码
负数:反码=正数的原码取反
补码=反码+1
然后用这个补码取反
例题:负三取反

- 条件运算符和表达式
- 运算符:表达式1?表达式2:表达式3
当表达式1真值为表达式2,假为表达式3,通常表达式1是关系和逻辑表达式
- 表达式:右结合
其实条件表达式也就是选择结构双分支的另一种表达形式,也就是if else
C语言运算符的优先性和结合性汇总
最最优先:()[ ] -> .(左结合)
- 单目运算符:! ++ --(前缀) +(正号)-(负号)* & sizeof ()强制类型转换 右结合
- 算术运算符(* / % > +-) 左结合
- 关系运算符(> >= < <= > == !=)左结合
- 按位运算符 》《 > & >^>|
- 逻辑运算符(&& > ||)左结合
- 条件运算符 右结合
- 赋值运算符(-= += /= %=) 右结合
- 逗号运算符 左结合
[2]控制结构
本章节主要学习算法的控制结构,也就是算法的执行顺序,控制结构分为三大类型,分别是顺序结构,分支(选择)结构,循环结构,分支结构根据不同的条件进入不同的分支,选择结构有三种类型:单分支,双分支,多分支,有两种语句:if和switch。循环结构有当循环(先判断后执行)和直到循环(先执行一次后判断)之分,让循环停止也有break和continue两种不同的方式。
2.1分支(选择)结构
1.单分支:if(表达式)语句
2.双分支:if(表达式)语句1
else语句2
3.多分支:
(1)if…elseif嵌套语句,如图
,
- switch:
switch(表达式)(4)
{
case常量表达式1:语句组1;(break;)
case常量表达式2:语句组2;(break;)
:
:
default:语句组n+1
}
注意注意case后面只能是常量表达式或者常量,不能是变量
所以case y+2一定是错误的,y是变量
Switch的流程是匹配表达式和常量表达式,然后从对应的case开始执行,遇到break才停止,没遇到就一直执行直到switch结束
- 选择结构嵌套(有点麻烦,具体题目碰到再分析)
易错题

2.2循环结构
(循环次数=循环体执行的次数)
总的来说就是一句话:当循环条件成立时,执行循环体的内容,循环条件不成立,就执行循环体外的语句
- While循环语句
While(表达式){循环体},下面是求整数因数的和的例题

2.do…while循环语句
与while的区别是无论是否满足循环条件都要先执行一遍循环体
do{ 循环体 }while(表达式);
例题:

- for循环
for(表达式1;表达式2;表达式3)
{循环体}
注意:a.循环体结束后才执行表达式3
b.如果没有表达式2,就是死循环
c.表达式1和3可以是逗号表达式,表达式1和3的值是逗号表达式最后面那个,但是for循环不看表达式的值,表达式1和3用逗号运算符的意思分别是执行多个初始化操作以及执行多个更新操作
d.for(;;)的意思是while(1)
2.3 break和continue语句
1.break
(1)用于循环结构和分支结构中的switch语句(不用于if else)
(2)循环里面是结束整个循环结构,执行循环结构后面的语句
分支switch结构是跳出switch结构,执行选择结构后面的语句
2.continue
(1)用在循环结构里面
(2)结束本次循环,强制进行下一次循环
注意注意break和continue都只能跳出当前所在层循环
[3]函数
通过本章我们将学习函数的定义,函数的参数,函数的调用以及函数的返回值,其中的难点在函数的嵌套调用和递归调用。然后还将了解变量的作用域和储存属性,其中static对变量的影响也要掌握。
-
- 函数定义和函数的返回值
1.函数定义:函数一定要先定义和声明后才能使用
从函数形参列表有无参数的角度,函数可以分为有参函数和无参函数
无参函数的定义:
类型标识符 函数名()
{
函数体
}
有参函数的定义:
类型标识符 函数名(形式参数列表)
{
函数体
}
我们可以看到二者的区别就在于括号里面有没有形参
注意:其中类型标识符的意思就是函数的返回值的类型,如果函数没有返回值,就是空类型void。
当省略类型标识符的时候默认为函数返回值是int型。
函数名要是合法的用户标识符
2.函数的返回值
int main(){}
从有无返回值的角度,函数可以分为有返回值和无返回值
return的功能就是使函数从被调函数返回主调函数,并且返回值 可以写成
return(表达式)
如果是没有返回值的函数就可以不写return,或者写成return;
函数的返回值类型由类型标识符决定
3.2 函数参数
1.函数参数:主调函数括号里面的是实际参数,被调用函数括号里面的形式参数
注意注意:
- 定义函数时,必须指定形参的类型和变量名eg:int a(int b,int c)
Int a(int b,c)的写法是错误的 ,定义函数时形参的每一个类型和变量名都要写
(后面改变,定义函数时,形参类型必须写,变量名可以不写)
- (声明函数时,可以只写形参的类型eg:int a(int,int))
- 形参变量只有当函数被调用时才分配内存,函数调用结束,形参所占用的内存就消失
- 实参可以是常量变量表达式,它的要求是一定要有确定的值
- 实参向形参是值传递,也就是单项的传递,形参不能改变实参的值,形参和实参占用不同内存单元,后面学习指针可以让二者指向同一内存单元,达到形参可以修改实参的目的
补充:一维数组和函数的结合
- 数组名作函数形参
A.函数形参和实参指向同一内存空间,一方值的改变影响另一方
B.形参数组可以指认长度,也可以不指认
C.实参可以不用数组名,可以用数组的指针变量来代替
- 数组元素作函数形参
A.单向的值传递
- 数组元素地址作函数形参
A.一方值的改变影响另一方
3.3函数的调用
1.函数调用的方式
(1)函数语句:fun();没有返回值的时候用
易错题:

(2)函数表达式:c = fun(a,b)和 sum = sum + fun(k)类似的函数在表达式里面的,有返回值的时候使用
(3)函数调用的返回值作为参数:fun(fun(a,b),c)
有返回值的函数被调用可以作为实参,但是不能作为形参
形参的要求:有参数的类型和名称,而返回函数值是一个值,不能作为类型和名称
2.函数的嵌套调用
函数内部不能定义函数,但是可以调用函数

特殊函数的嵌套调用->函数的递归调用
直接递归调用:定义函数a,在函数a里面调用函数a
return fun(n-1)+ fun(n-2);
间接归递调用:定义函数a,在函数a里面调用函数b,函数b里面调用函数a
3.4 变量的作用域与储存类型
1.变量的作用域(变量的作用范围)(局部变量和全局变量)
(1)局部变量:定义在函数内部的变量,局部变量只在本函数体内有效
注意注意:a.main函数里面的变量也只在main函数内部有效
b.形式参数也是局部变量,其他函数不能用
c.复合语句的变量只能在复合语句内使用,离开复合语句变量就无效,释放内存单元

- 全局变量:定义在函数外部的变量,也叫外部变量,作用范围是从定义全局变量的位置开始到本源文件的结束,作用域范围内的所有函数都能对其读取和修改(如果想全局变量在靠后的位置,但是前面的函数又想使用全局变量,就可以加上extern,扩宽全局变量的作用域)
注意注意:全局变量的重要作用是增加与函数联系的渠道
a.全局变量的值只初始化一次,后面使用全局变量接着前面的值运算,不会反复初始化

b.主函数引用全局变量可以获取多个值

- 全局变量赋初始值只能在定义中使用,不能在说明(extern)的时候用
d.当局部变量和全局变量同名时,在局部变量的作用域内,全局变量不起作用
举例子+补充知识点(!重要)


注意注意,fun()函数虽然没有返回值,但是fun函数内部有printf的内容,还是会被输出
相似题型

2.变量的储存属性(静态储存方式和动态储存方式)
(1)静态储存方式:程序运行期间分配固定的储存空间
动态储存方式:程序运行期间动态的分配储存空间
(2)变量的声明:储存类别 数据类型 变量名 (=初始化)
变量考虑:储存类型(的不同影响变量的生命周期)和作用域(局部/全局)
A.局部变量的声明:
·自动变量(auto):调用函数时分配内存,函数调用完后释放内存,函数中的局部变量只要不专门声明static类型,就都是auto,也就是动态储存方式,auto可以省略
·静态局部变量(static):调用函数结束后仍然保存局部变量的值,它属于静态储存区,在程序的整个运行期间内都不会释放。
注意注意:
静态局部变量的初始值只赋一次,该变量的值就是上次函数调用结束后的值;
如果没有给静态局部变量赋初始值的话,默认它的值为0;
静态局部变量在函数调用后仍然存在,但是其他函数是不可以调用他的,
原因:静态局部变量的生命周期是整个程序,但是作用域是其存在的函数
·寄存器变量(register):相较于局部变量放在cpu里面,放在寄存器里面大大提高了效率
B.全局变量的声明:
·extern:主要用来扩宽变量的作用域,可以让变量从声明处开始可以合法的使用该变量
·static:静态全局变量主要作用主要是使变量只限于被本文件使用,而不能被其他文件使用
3.5 函数补充
1.函数声明:
函数类型 函数名(参数类型1 参数名)
其中参数名可以省略
2.函数分类:
函数根据能否被其他文件中的函数引用分类:内部函数和外部函数
内部函数:static 函数类型 函数名(形参表){函数体}
这个函数只能被本文件的函数引用,称为内部函数也叫做静态函数
外部函数:extern 函数类型 函数名(形参表){函数体}
这个函数可以被其他文件引用,extern一般省略
3动态储存分配
- malloc函数: void* malloc(size)返回值为空指针
记得强制类型转换:(int *)malloc(sizeof(int)*2)
- calloc函数: void* calloc(元素个数,字节大小)
(int*)calloc(3, sizeof(int))
- free
free只能用于释放malloc/calloc/realloc分配的堆内存
free只能用于释放malloc/calloc/realloc分配的堆内存
例子:

[4]数组
数组是一种重要的数据类型,简单来说数组就是众多数据类型相同的变量的集合,数组根据下标数量有一维数组和二维数组之分,还有多维数组,但是我们不做过多了解。
数组:数组的长度是固定的,数组元素的类型必须是相同的数据类型
4.1一维数组的定义,初始化,引用
1.一维数组的定义:数据类型 数组名[常量表达式]
Eg:int a [10],指这个数组名为a的数组,一共有10个元素,也就是a[0]到a[9],并且每个元素的数据类型都是int型
注意注意:(1)常量表达式一定要是常量,可以是符号常量,但是绝对不可以是变量
这个写法是错误的:int a = 10;int b[a];
(2)数组的下标是从0开始,到数组元素-1结束
(3)数组名表示数组中所有元素在内存中的储存地址,数组是地址常量,不可以当成变量进行修改
(4)数组所占的字节数=数组元素个数*sizeof(数据类型)
2.一维数组的初始化:初始化也就是在定义变量的时候对其进行赋值
数据类型 数组名[常量表达式] = {初始化表};
- 可以只给部分元素初始化:int a[10]={1,2,3,4};未初始化的部分当成0
- 当给全部元素初始化的时候,可以省略元素个数
int a[ ]={1,2,3,4,5};这个时候系统给常量表达式赋值5
- 如果不给数组赋值,数组元素的值是不确定的,解决方案,可以赋值为0
int a[5]={0};
- 一维数组元素的引用:数组元素不能全部引用,要一个一个引用
(补充:数组和指针的区别
数组:数据必须连续储存,因为数组数据的访问靠的是“基数据+偏移量”
a[0],a[1]=a[0]的地址+4字节
指针: 数据不用连续储存,因为指针储存的是数据的真实地址,不用依靠偏移,这也是指针高效利用内存的原因)
数组名[下标表达式],下标表达式可以是整型常量,整型变量,整型表达式
例如a[2*3]
- 输入10个数组元素:for(i=0;i<10;i++)scanf(“%d”,&a[i]);
注意注意:数组的长度和下标是两个不同的概念
int a[10]表示有10个数组元素,也就是只能有效的引用a[0]到a[9]
4.2二维数组的定义,初始化,引用
1.二维数组的定义:数据类型 数组名[常量表达式1][常量表达式2]
Eg:int a[3][4]定义a是一个3行4列的整型二维数组,一共有12个整型变量,其中行和列的下标最大不会超过[常量表达式1-1][常量表达式2-1]
注意注意:a.二维数组的数组元素在内存里面是按照行来储存的(这也是二维数组初始化可以用一维数组初始化的原因),也就是一个连续的一维数组
b.二维数组可以看成一个特殊的一维数组
eg:int a[3][4]可以看成一个包含3个元素的一维数组,也就是a[0]a[1]a[2],而三个元素本身也就是一个包含4个元素的一维数组,例如a[0]包含了a[0][0],a[0][1],a[0][2],a[0][3]这三个元素
2.二维数组的初始化
(1)按行赋值int a[2][3]={{1,2,3},{4,5,6}};
(2)按一维数组的方式赋值 int a = {1,2,3,4,5,6};
(3)按行赋值,只赋一部分 int a[2][3]={{1,2,3},{0}};数组其余部分就是0
(4)如果对数组全部元素赋值,可以省略第一维数组的长度,但是不能省略第二维数组的长度eg:int a[][3]={1,2,3,4,5,6}是正确的
3.二维数组的引用
(1)数组名[下标1][下标2],下标是整型常量,整型变量,整型表达式
(2)引用二维数组的方式:利用两个for循环
int a[2][3],i,j;
for(i=0;i<2;i++)
for(j=0;j<3;j++)
a[i][j] = i+j+1
4.3字符数组
1.字符数组的定义
char 数组名[常量表达式1] 一维字符数组用于储存一个字符串
char 数组名[常量表达式1][常量表达式2] 二维数组用于储存多个字符串
eg:char a[2][10]={“hello”,“world”};
2.字符数组的初始化
(1)储存多个字符数据:char a[5]={‘a’,‘b’,‘c’,‘\0’};
如果省略数组长度,则系统自动按照初始化个数来确定数组个数
例如:char b[]={‘a’,‘b’,‘c’,‘\0’};可知数组长度是4
(2)储存字符串常量:char a[6]={“hello”}(没必要)或者写成char a[6]=“hello”;
易错:char a[]={‘a’,‘b’,‘c’};没有\0,不能当做字符串数组使用,仅仅是一个字符数组,不能用字符串函数strlen等
初始化后也可以省略数组长度
注意注意:
a.第二种方式是储存字符串常量‘\0’作为字符串常量的结束标志,它算在数组长度里面,但是不是有效字符
数组长度:编译器分配的内存空间大小(sizeof)
有效字符数:实际储存(strlen)
b.字符串来初始化字符数组的时候,有效字符数至少比字符数组长度少1
3.字符数组的引用
(1)字符数据的输入和输出
(2)字符串的输入和输出

- 字符串的有关函数
这些函数可以很方便的对字符串进行处理,如字符串的连接,复制,比较,测试有效长度等,但是记住只能用于字符串且在程序开始前应该做包含处理#include<string.h>

Strncat(a,b,3),就是把b字符串的前三个字符拼接到字符串a的后面,然后自动补\0 ,也就是chinabei
例题strcat

补充:strcspn()指在字符串1中首次出现字符串2的个数
常用用法:
fgets(buf, sizeof(buf), stdin);
// 第三个参数stdin表示从键盘输入
// 2. 用strcspn找到换行符的位置,替换为'\0'(核心步骤)
// strcspn(buf, "\n") 返回buf中第一个'\n'之前的字符数(即换行符的下标)
buf[strcspn(buf, "\n")] = '\0';
[5]指针
本章节主要学习指针变量的定义和使用,并能通过指针来引用数组和字符串,还有学习指针和函数的结合,比如指针作为函数参数,数组指针作为函数参数,指针作为函数的返回类型等
5.1 指针的定义和运算
(1)指针有关概念(一定要懂!)
(前言:存储器中一个字节是一个内存单元,定义变量类型后,分配内存单元,每个内存单元都要编号,这个编号就是内存单元的地址。)
指针:指针就是内存单元的编号,也就是地址
指针变量:定义一个变量来储存指针(地址),指针变量的值是某个内存单元的地址
指针是常量,指针变量是变量
指向:例如p是指针变量,p储存了变量a的地址,则表示为p指向a,如果a是int类型,则说p是指向整型变量的指针变量
(2)指针变量的定义
类型说明符 * 指针变量名
Char *p 说明p是指向字符型变量的指针变量
(这里来说明一下为什么p储存的是地址值,还要有类型说明符char,这是因为指针变量指向不同的数据类型,会对指针变量的移动和计算有影响,char类型指针变量的地址移动1个字节,int类型指针变量的地址移动4个字节,因此指针变量必须有类型说明符)
(3)指针变量的引用
变量都要先定义后使用,指针变量还要加一条必须赋予指针变量具体的值,不然就是野指针

- 指针变量的赋值
- 变量地址 int *p=&a;
- 数组名 int a[10], *p;p=a;(p=&a[1];)
- Null 空指针
- 指针变量赋值(注意一定要相同类型,如果指针变量类型不同要进行强制类型转换)
- 指针的运算
- 指针变量加减整数运算(只能当指针指向数组的时候使用)
p--,p++,p=p+n
指针变量的地址=指针变量的起始地址 加或减sizeof(指针类型)

- 两个指针相减的运算(指针必须指向同一数组)
表示指针之间包含的元素个数,也就是将指针的地址互减,然后把结果除以数组元素的长度
- 指针之间的关系运算
表示所指数组元素之间的位置关系
P>q,表示p指向的元素在高地址位置
P==q,表示p和q指向数组的同一元素
5.2 指向一维数组的指针(运用指针变量来表示数组元素)

指针与一维数组的联系
- p[i],a[i] ,*(a+i),*(p+i)表示数组i的元素
- p+i,a+i,&a[i]表示数组元素a[i]的地址
- *p++,*(p++)表示指针先解引用当前值,后移动指针p++
- (*p)++表示先取当前值,后结果加1
- *(++p)表示先移动指针后解引用
用指针来控制循环并且实现数组值逆序

5.3指向二维数组的指针
看懂下图!!(很重要)

重要例题:

二维数组名a = &a[0],也就是首行的指针
对行指针进行解引用*(a+i)表示行指针首元素的地址=&a[i][0]
判断指针表达式的值是不是相同看:地址值是否相同+指向的地址内存位置是否相同
例如a和a[0],a表示行指针指向&a[0],而a[0]指向&a[0][0],两者细节不同,但是整体都是指向&a[0][0]的地址,可以说两者相同
5.4行指针和指针数组
(1)行指针变量(为多维数组设置的特殊指针变量)
一般格式:类型说明符 (*p)[二维数组的列数]
Int a[3][3],(*p)[3];
行指针p指向每行有3个int类型元素的二维数组
下图必懂!!

扩展:用行指针求每行值的和(对行指针进行解引用,表示当前行首元素的地址,p=a=&a[0],*p=a[0]=*(a+0)+0也就是元素a[0][0]的地址,而代码里面(*p)[j]则表示行指针p指向当前行数的第j个元素的值,这是另一种写法,还可以写成*(*(p+i)+j)),也表示元素a[i][j]的值)

(2)指针数组
一般格式:类型说明符 *数组名[数组长度]
指针数组本质是数组,不过这个数组里面的元素都是指针,类型说明符用来说明指针所指向的变量的类型。
指针数组一般用来储存一组字符串,储存的是每个变量的地址
5.5 指向字符串的指针
(存取字符串的方式有两种,
第一个是字符数组
Char a[]={“hello”};
(数组名也就是地址常量只能初始化,不能后面定义)
Printf(“%s”,a);
第二个是通过字符串指针,存储字符串的首地址
Char *a=“hello”;
(不能写成{“hello”})
Printf(“%s”,a);)
字符串指针变量和字符指针变量的定义都是char *a,区分二者的主要方式是赋值的内容
5.6 指针作为函数参数
(1)函数的参数不仅可以是整型,实型,字符型,还有可能是指针类型。
指针类型传递也就是地址传递,可以直接操作原始数据的内存空间,而值传递相当于拷贝一份局部变量到被调用函数,而局部变量储存在栈区,调用结束后就释放,所以值传递不能通过改变形参变量的值改变实参变量
地址传递:

值传递:


(2)一维数组指针作为函数参数
实参是数组,形参可以是数组也可以是指针变量
- 二维数组指针作为函数参数
实参是二维数组,形参可以是二维数组也可以是行指针(二维数组名表示首行的地址)
5.7指针型函数和函数指针
(1)指针型函数
本质是函数,不过它的返回值是指针类型的,也就是地址值,如果在主函数里面调用该函数还要定义一个指针,指针指向被调函数的返回值
格式:类型标识符 *函数名(形参列表)

(2)函数指针
本质是一个指针,这个指针是指向函数的一个指针变量,直接将该指针指向函数名即可,函数名表示函数的起始地址
格式:类型标识符 (*指针变量名)(参数表)

当定义函数指针的时候,怎么调用它!!下图搞懂
(对函数指针解引用,它的值是函数的地址)

字符指针数组
其中数组名str表示&str[0],也就是二级指针

//字符指针数组把它当成一维数组,**p=str,*(p+i)表示这个数组中第i个元素的值,只不过这个里面储存的值是指针变量
//二维数组中行指针p,*(p+i)表示该行首元素的地址,这个地址是常量,不是字符指针变量里面的那种储存指针值的变量,这个里面的地址是本身就是地址
[6] 结构体与共用体
本章学习的内容是一种新的数据类型,也就是构造类型(复合类型),感觉是数组的plus版本,数组只能包含相同的数据类型,而结构体里面可以存放不同的数据类型
-
- 结构体类型的定义+结构体类型变量的定义
- 结构体类型的定义:
Struct 结构体名{
类型说明符 成员名1;
类型说明符 成员名2;
};
结构体包含的数据元素称为成员(结构体是一个整体,结构体的成员都不能脱落结构体单独使用)
- 结构体类型变量的定义

- 结构体数组的赋值

不要p.a=“hello”;该写法是错误的,a是字符数组,是地址常量,不能赋值
更多推荐


所有评论(0)