C语言指针,数组,函数总结

引子:数据在内存中是如何存储的,又是如何读取的?内存编号就是内存的地址( 内存中每个字节都有一个编号, 即地址)
1. 概念:e
地址:内部存储器的编号,称为地址。如变量a 的位置编号,变量b 的位置都是指针。
指针变量:专门存放地址的变量称为指针变量。
地址、指针、指针变量都称为指针。
一、 变量的地址 ( 指针 ) 和指向变量的地址变量 ( 指针 )
1. 概念:
变量的指针: 就是变量的地址。
指针变量: 是用来存放地址的变量,普通变量是用来存放数据的,指针变量是存放地址的。
2. 定义地址变量:
1)         格式: [ 存储类型 数据类型    * 指针变量名;
int i, j;
int * pointer1, *pointer2;
存储类型是这个地址变量的存储位置
数据类型指的是这个地址变量指向的目标变量类型,不代表本身的类型大小。
2)         指针变量的赋值:
方法一:
int  a = 5;  int *p = &a; // 定义并初始化。
方法二:
int  a = 5; int *p;  p = &a;//  先定义后赋值。
PS:
定义时,int *p中*是为了说明该p是地址变量,用来存放地址;
定义地址变量时必须指定数据类型,不同类型指针不可相互赋值;
指针变量的数据类型不表示变量的类型,是表示该变量指向的目标的数据类型,访问内存时读取的内存空间大小。
3. 指针变量引用
1)         * & 符号
  * 定义指针变量/ 取地址对应的变量的内容( 间接访问) //i = 3 直接 ,*p=3 间接
  & 取变量的地址。
 
* & 互为逆运算。自右向左
3)         引用
Ø   对指针变量赋值
p = &a;
Ø   引用地址变量指向的内容
printf( “a=%d\n”,  *p );
Ø   引用变量本身的内容( 即存储的地址)
printf(“%x\n”,  p);
eg:
int i = 188; int *p = &i;
p 指针变量,内部存放的是目标的地址;
*p 目标,目标内存数据;
&p 指针变量的内存地址;
p = &i = &(*p)
i = *p = *(&i)
4. 指针运算
指针运算就是地址运算,即指针变量中的地址作为运算量。
地址只能做算术、关系、赋值运算。
1)         算术运算
px + n 代表指针向地址大的方向移动 n 数据。
移动后的地址量是: (px) + sizeof(px 的类型) * n
px++  指针变量向地址大的方向移动一个数据。
      px - py 表示两个相同类型指针间相差数据的个数,而不是一个地址量。
px - py 的结果是  (px - py) /sizeof( 数据类型)
px + py 的结果? 没有任何意义
指针加发运算加的数值是增加地址本身类型的 N 倍大小 ( 这在数组中访问经常用到 )
 
4)         关系运算
C语言指针,数组,函数总结_第1张图片

指针关系表示两个指针在内存位置的高低关系。
不同数据区域间的指针,关系运算没有意义。
指针和除0 外的整数比较没有意义,和0 比较可以判定指针是否为空。( 标准写法为if (NULL == p) ).
5)         赋值运算
向指针变量传递一个地址值。这个值是地址常量或指针变量( 同类型) ,不能是普通整数(0 可以表示空值)
6)         const/void 指针
const 表示的使变量常量化,即不可修改。
int  const a = 9;
a = 10; // 报错,a const 修饰不可改变。
const 在遇到指针时会发生一些变化
const int a int const a const 可以在int 的左右位置。
int a = 9;
int b = 12;
const int *p = &a; // const 修饰的是*p , pa 指向变量a, int const *p = &a; //和上面效果相同, 都表示地址变量pa指向a,且*pa不可变
*p = 10 ; // 通过p改变a的值,但*p是const类型,不可改变。
p = &b; //可以改变p的值,(即指向)。
*p = 11;// 同样不可以
int *const q = &a; //const 修饰的是 q, 所以q是不能改变的,即不能改变q的指向
*q = 111;
q = &b; // 将q指向b,报错。
 
void 型指针
   指针变量指向不确定数据类型的变量的时候,可以定义为void 型指针,
因为void 类型指针可以赋值给其他任意类型的指针,而其他类型不能相互赋值.
如:malloc 函数
void * malloc(size_t size);
malloc 函数因为不知道分配空间的具体用途,所以返回void 型地址。
 
7)         多级指针
指向地址变量的地址变量,称为多级指针( 画图表示)
定义一个二级指针
int *p = &a;
int **q = &p;
 
8)         小结:指针自增与自减
ü  p++( p+=1) 使p 指向下一个元素
ü  *p++   ++ * 具有相同优先级且结合方向自右向左, 等价于*(p++) 先取*p 的值,然后p 再自加,指向下一个元素。
ü  *(p++) *(++p) 作用不同。 前者是先取*p 的值,再使p 自加。后者先使p 自加,再取自加后指向的内容。
ü  ++(*p) 表示将p 指向的元素的值加1.
 
二、 指针与数组
1. 指针与一维数组
数组的指针是指数组在内存中的起始地址,即第一个数组元素的地址.
一维数组的数组名代表一维数组的指针( 起始地址)
[ ] 又叫做变址运算符
a[i] <=> *(a+i) 在计算机内部实现的时候,数组下标都会转化为地址。
 
若地址变量px 的地址值等于数组指针x( 指针变量px 指向数组的首地址) ,则:
x[i] *(px+i) *(x+i) px[i] 具有相同功能的功能: 访问数组第i+1 个数组元素。
C语言指针,数组,函数总结_第2张图片
数组元素访问过程中,数组地址与指针变量具有相同的访问效果
不同: 地址变量是变量。
数组地址( 数组名) 是常量,不能自加或自减
1)         地址变量与数组的赋值
             1.  int  *p = &a[0];
2.  int *p;
p = &a[0];
            3.  int *p = a;
小结:
1. p+i a + i 就是a[i] 的地址, 指向a 数组的第i 个元素。
2. *(p+i) *(a+i) 是取a[i] 元素的值。
3. 指向数组的地址变量也可以带下标 p[i] *(p+i) *(a+i) 等效。
 
9)         指针与数组常见操作
数组 指针表示 含义
array &array[0] 数组名是第一个元素的地址
*array array[0] 数组的第一个元素
array + i &array[i] 数组第i个元素的地址
*(array + i ) *(&array[i]) == array[i] 数组第i个元素
*array + m array[0] + m 数组第一个元素加m
*array++ error error
 
经典例子:
一维字符 指针数组ps[5] 里面存放着字符串首地址
char  *ps[5] = {“beijing city”,  “New York”, “London”, “Paris city”, “Moscow city”};
       定义一个指针变量,并指向数组首地址;
       char **pps = ps; 那么ps 指向 指针数组的首地址
C语言指针,数组,函数总结_第3张图片
5. 指针与二维数组
定义一个二维数组a ,有3 4
int a[3][4] = { {1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23} };
a 是数组名, a 数组包含3 行,即3 个元素,分别是a[0] a[1] a[2] 。每个元素同样是一个一维数组,包含4 个元素,a[0][0], a[0][1], a[0][2], a[0][3].
a[0] 是一维数组名,代表的是第一行的第一个元素的首地址,a[0] = &a[0][0].
a[1] 是一维数组名, 代表的是第二行的第一个元素的首地址, a[1] = &a[1][0].
a[0][0] 是一个元素
&a[0][0] ==> a[0] 取一维数组的首地址,即一维数组名a[0], (a[0] 为第一行首地址)
&a[0] ==> a 取a[0], a[1], a[2[ 三个元素中的首地址,即数组名a.
&a == &a 取数组a的地址,即取数组的位置。

a 指向? (指向第0行首地址) *(a+0) == a[0];
a+1 指向? (指向第一行的首地址) *(a+1) == a[1];
*(a+0) + 1 是a[0][1]的地址, *(*(a+0) +1) == a[0][1]
*(a+1) + 1 是a[1][1]的地址, *(*(a+1) + 1) == a[1][1] == *(a[1] + 1)
PS:
对数组名取值就得到数组元素;
对数组元素取址就得到当前数组地址;
a[0][0] =(&a[0][0]) => a[0], 对第0行首元素取地址得到地址;
a[0] =(&a[0]) => a, 对3个元素的第0个元素取地址得到数组名。
a =(&a) => &a, 对数组名取地址,得到数组地址。
思考:
&a +1 指向? (指向下一个数组,增加一整个数组);
a+1 指向? (指向下一行首地址, *(a+1) == a[1] );
*(a+1) + 1 指向? (指向a[1][1], *(*(a+1) + 1) == a[1][1] );
注意点:
a + 1 表示的是第一行首地址 ,  a 表示是行数组的地址变化。
a[1]  表示的是第一行 0 列的地址 &a[1][0] a[1] = * (a + 1); a 表示的是当前行的首地址变化
 
1)         二维数组的行地址、列地址
行地址:二维数组名是个特殊的地址,参与运算时以行为单位移动,因此被称为是行地址。如int a[2][3],  a 代表的是第一行的首地址,a + 1 代表第二行的首地址。
列数组:int *p = a
printf(“%d”, *(p+i) ) p+i 相当于移动了i 列, 因此指针p 为列指针。
综上:
   a+1 a[0] + 1 表示的地址值是相同的, 但含义是不同的, a+1 是序号为第一行的首地址, a[1] *(a+1) *a[1] + 1 指向1 0 列元素。
   二维数组名是指向行的,在行指针前面加上* 就可以转换为指向列的指针。
eg
a a+1 是指向行的地址;
*a *(a+1) 是指向列的指针。
反之,在列指针前面加上& ,就可以转换为行指针。
eg
a[0] 指向0 0 列的列元素指针, &a[0] *(a + 0)
int main()
{
int a []={5,8,7,6,2,7,3};
int y,*p=&a[1];
y=(*--p)++;
printf(“%d ”,y); // 5
printf(“%d”,a[0]); //6
}
 
10)     指向元素的指针变量和指向数组的指针变量 ( 数组指针 ) 比较
ü   指向数组元素的指针变量定义和普通指针变量相同
C语言指针,数组,函数总结_第4张图片
 
ü   指向由m 个元素组成的一维数组的指针变量:
  
C语言指针,数组,函数总结_第5张图片
对数组指针赋值:a 为二维数组名:
p = a; p = &a[0];
int (*p)[4] 4 表示一维数组长度,且*p 两端的括号不可省略, 方括号的优先级高, int *p [4] p 会先和[ ] 组成数组 p[4] 然后再和* 组成 指针数组( 地址数组)
p 指向的是行的起始地址, *p 相当于p[0]
6. 字符串与字符指针
C 中没有字符串数据类型。通常借助字符数组来存储字符串。
字符指针可以存储字符串的起始地址,即指针会指向字符串第一个字符。
字符串指针初始化,可直接把内存中字符串的首地址赋予指针变量。
eg
char *s = “Welcom!”; // 相当于const char *s = “welcome!” 。字符串常量是存放在常量区的, 不可修改。
7. 指针数组
指针数组即这个数组元素里存放的是地址( 相同存储类型和数据类型的地址集合)
1)         说明形式:
< 存储类型>  < 数据类型>  *< 指针变量数组名> [< 长度>]
int *p[2];
char  *c[9];
11)     指针数组名含义:
表示这个数组的存储首地址,即指针数组名为数组的地址。( 和常规的数组相同, 不同的是这个数组存储地址)
指针数组中的元素就是地址,要取得元素对应的值,直接对数组元素取值即可,或对数组名**(p+i) 取值。
C语言指针,数组,函数总结_第6张图片
指针数组元素就是地址,数组名代表数组的起始地址, 数组名是地址的地址,即二级指针。
指针数组与数组指针的比较:
数组指针:
int (*p)[n] , p是一个指针,指向整个一维数组,这个一维数组长度为n, p+1时,p增加整个数据的长度。
数组指针也称为指向一维数组的指针, 或叫行指针。
指针数组:
int *p[n], p是一个数组,这个数组元素是地址,p+1,指向下一个数组元素。
p = a属于赋值错误,因为p是常量。只能对元素赋值a[0] = a, *p = a。
 
三、 函数
1. 函数定义、声明和调用
定义:
形式:
< 数据类型>  < 函数名称> (< 形式参数说明>)
{
语句集;
return (< 表达式>)
}
无参函数。 有参函数,空函数
参数类型:变量,指针,数组名,数组指针,函数指针。。。。
声明:函数在声明中可以省略形参,但这样编译器就不能检查实参和形参是否匹配, 所以不建议省略。  
  C语言指针,数组,函数总结_第7张图片
8. 函数传参与返回值
Ø   函数未调用时,形参没有分配空间。调用时,系统为形参分配空间。调用结束后,所占内存单元被释放。
Ø   实参可以是常量、变量或表达式。max(a, a+b);
Ø   函数定义时要指定数据类型。
Ø   实参与形参的类型相同或兼容。
Ø  C 语言中,实参向形参传递参数是单向的,只能由实参传递给形参,反之不可以,在内存中,实参和形参占用的是不同的内存单元。
1)         值传递与址传递
值传递
C语言指针,数组,函数总结_第8张图片
( 分析值传递过程,值传递过程相当于隐含动作int a = x, int  b = y)
 
址传递
C语言指针,数组,函数总结_第9张图片
典型strcpy 实现
char * strcpy(char *dest, const char *source)
{
asssert(dest != NULL && source != NULL);
char * r = dest;
while ((*dest++ = *source++) != ‘\0’)
;
return dest;
}
 
12)     返回值
ü   函数的返回值由return 语句返回,如:return  z return (z) return ( 表达式) 三种形式都可以,括号可以省略,保持简洁。
ü   函数值在类型不指定时,系统按整型处理。
ü   函数定义类型和返回类型保持一致。
ü   void 定义的函数表示空类型;
9. 函数调用
1)         调用形式
Ø   函数语句:printf(“”);
Ø   函数表达式: a = read(buff,  fd, n);
Ø   函数参数: m = max(a, max(a, b));
Ø   函数调用函数的几点说明:
被调用函数必须是存在的库函数或自定义函数;如果使用库函数,要在文件开头进行#include <> 进行头文件包含,对函数进行声明。
调用自定义函数时,被调用函数应该在调用函数前面, 在主函数中进行被调用函数的声明。
声明可以函数声明可以在主函数内部或在主函数前面,也可以在主函数前面进行函数定义。函数类型省略的情况下,系统默认是int .( 思考:两个函数相互调用怎么解决先后问题?)
 

你可能感兴趣的:(嵌入式C语言)