C指针学习

  • &运算符:

    • 一元&运算符给出变量的存储地址;eg:pooh是变量名,&pooh–变量的地址(地址–变量在内存中的位置)
  • 指针(pointer):

    • 用于存储变量的地址。
    • 从根本上来看,指针是一个值为内存地址的变量(或数据对象);正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。
  • 如何把指针作为函数参数使用,以及为什么。

  • 间接运算符*(indirection operator)/解引用运算符(dereferencing operator)

    ptr = &bah;
    val = *ptr;
    //等价于
    val = bah; 

与指针相关运算符

  • 地址运算符:&

    • 后跟一个变量时,&给出该变量的地址。
    • &nurse表示 变量nurse的地址。
  • 地址运算符:*

    • 后跟一个指针名或地址时,*给出储存在指针指向地址上的值(地址空间中的值)。
    nurse = 22;
    ptr = &nurse; //指向nurse的指针
    val = *ptr;  //把ptr指向的地址上的值赋给val
    //结果:把22赋值给val
    

声明指针变量

  • 声明指针变量时必须指定指针所指向变量的类型,因为:
    1. 不同的变量类型占用的存储空间不同,一些指针操作要求知道操作对象的大小。
    2. 程序必须知道储存在指定地址上的数据类型。

指针声明示例:

int * pi;   //pi是指向int类型变量的指针
char * pc;  //pc是指向char类型变量的指针
float * pf, * pg   //pf、pg都是指向float类型变量的指针
  • 类型说明符表明:指针所指向对象的类型。
  • *:声明的变量是一个指针。
  • int * pi;:pi是一个指针,*pi是int类型。

函数调用形式:

  1. function1(x); //传递的是x的值。函数定义int function1(int num)
  2. function2(&x); //传递的是x的地址。函数定义int function2(int *ptr)

若要计算或处理值,使用第1种函数调用。
若要在被调函数中改变主调函数的变量,则使用第2种方式。

变量:名称、地址、值

  • 编程时,可认为变量有两个属性:名称、值
  • 计算机编译和加载程序后,可认为变量有两个属性:地址、值
  • c中,可通过&运算符访问地址;eg:&bran–bran的地址。
  • 使用*运算符获得储存在地址上的值。
  • 普通变量把值作为基本量,把地址作为通过&运算符获得的派生量。
  • 指针变量,把地址作为基本量,把值作为通过*运算符获得的派生量。
  • 使用&*、指针 可以操纵地址和地址上的内容。

函数

传递值:

  • 被调函数一般不会改变主调函数中的变量,若要改变,需使用指针作为参数。
  • 若希望多个值传回主调函数,必须使用指针作为参数。

函数的返回类型:

  • 函数的返回类型指函数返回值的类型;若返回值的类型与声明的返回类型不匹配,返回值将被转换成函数声明的返回类型。

函数签名

  • 每个函数都应该有一个单独且定义好的功能。
  • 使用参数把值传递给函数,使用关键字return把值返回函数。
  • 若函数返回值不是int类型,则必须在函数定义和函数原型中指定函数的类型。
  • 若要在被调函数中修改主调函数的变量,使用地址或者指针做为参数。

数组和指针

声明一些数组:

int main(void)
{
    float candy[365];  //内含365个float类型元素的数组
    char code[12];  //内含12个char类型元素的数组
    int states[50];  //内含50个int元素的数组
}
  • 方括号[]表明candy、code、states都是数组,方括号中的数字表明数组中的元素个数。
  • 通过数组下标(索引)访问数组中的元素,数组元素编号从0开始;candy[0]–candy数组第0个元素,candy[364]–candy数组第365个元素。
  • 使用const声明数组
    • 把数组设置为只读,用const声明和初始化数组。
    • 一旦声明为const,就不能再给它赋值。

数组初始化:

  • 使用数组前必须先初始化。
  • 初始化省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小(可以省略方括号中的数字,让编译器自动匹配数组大小和初始化列表中的项数。)
  • 计算数组大小,sizeof(days) / sizeof(days[0])
  • 指定初始化器。

给数组元素赋值:

  • 借助数组下标(索引)给数组元素赋值。

数组边界:

  • 在使用时,防止数组下标超出边界;必须保证下标是有效的。

多维数组

指针和数组

  • 指针提供一种以符号形式使用地址的方法。计算机的硬件指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,使用指针的程序更有效率。
  • 指针能有效处理数组,数组表示法其实是在变相地使用指针。
  • 数组名–数组首元素的地址。filzny是一个数组,则有:
flizny == &flizny[0];  //数组名--数组首元素地址
- `flizny`和`&flizny[0]`都表示数组首元素的内存地址。两者都是常量;可把它们的值赋给指针变量,然后可以修改指针变量的值。
- `%p`通常以16进制显示指针的值。
  • 在C中,指针+1;指的是增加一个存储单元。
    • 对于数组,+1后的地址是下一个元素的地址;而不是下一个字节的地址;这是为什么必须声明指针所指向对象类型的原因之一。(只知道地址不够,因为计算机需要知道储存的对象需要多少个字节)。
int dates[y], *pti;
pti = dates;  // 等价于 pti = &dates[0]; 把数组dates首元素地址赋给指针变量pti
  • 指针的值是,它所指向对象的地址。地址的表示依赖内部硬件,许多是按字节编址(内存中的每个字节都按顺序编号);一个较大对象的地址,通常是该对象第一个字节的地址。
  • 在指针前使用*运算符可以得到该指针所指向对象的值。
  • 指针+1,指针的值递增它所指向类型的大小(字节为单位)。
  • 数组和指针的关系十分密切:
dates + 2 == &dates[2];  //相同的地址
*(dates + 2) == dates[2]  //相同的值
- 可以使用指针标识数组的元素和获得元素的值;本质上看,同一个对象,有两种表示法。
- 定义`ar[n]`的意思是`*(ar + n)`;`*(ar + n)`的意思是,**到内存的`ar`位置,然后移动n个单元,检索储存在那里的值**。
- 间接运算`*`的优先级高于`+`;`*(dates + 2) != *dates +2`;`*dates + 2`相当于`*(dates) + 2`。
```
*(dates + 2)  //dates第三个元素的值
*dates +2  //dates第1个元素的值+2
```
- 可以用数组表示指针,也可以用指针表示数组,在使用以数组为参数的函数时需要注意。

函数、数组和指针

编写一个处理数组的函数,返回数组中所有元素之和,数组名marbles,数组类型int型。

int sum(int *ar, int n);  //函数原型  等价于int sum(int ar[], int n);

int sum(int * ar, int n)  //第一个形参--告诉函数数组的地址和数据类型,第二个形参--数组中元素个数
{
    int i;
    int total = 0;
    
    for(i = 0; i < n; i++)
    {
        total += ar[i];  //使用数组表示指针,ar[i] 和 *(ar + i)相同。
    }
    return total;
}

  • 只有的函数原型/函数定义头中,才可用int ar[]代替int * ar: int sum(int ar[], int n);
  • int *arint ar[]都表示ar是一个指向int的指针;但int ar[]只能用于声明形式参数;int ar[]提醒读者指针ar指向的不仅仅是一个int类型值,还是一个int类型数组的元素。

指针操作

  • 赋值:可把地址赋给指针。eg:用数组名、带地址运算符(&)的变量名、另一个指针进行赋值。
  • 解引用:*运算符给出指针指向地址上储存的值
  • 取址:指针变量也有自己的地址和值,对于指针而言&运算符给出指针本身的地址。
  • 指针与整数相加:可使用+运算符把指针与整数相加,后整数与指针相加。两种情况,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加
  • 指针减去一个整数:可使用-运算符从一个指针中减去一个整数。指针必须是第一个运算对象,整数是第二个运算对象;该整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积
  • 递增指针
  • 递减指针
  • 指针求差:可以计算两个指针的差值;通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。
  • 比较:使用关系运算符可比较两个指针的值,前提是两个指针都指向相同类型的对象。

note:
减法有两种:1.一个指针减去另一个指针得到一个整数。2.一个指针减去一个整数,得到一个指针。
不能解引用未初始化的指针

int * pt;  //未初始化的指针
*pt = 5;  //严重错误

创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存;在使用指针之前,必须用已分配的地址初始化它。

基于这些基本操作,在C中有:

  • 指针数组
  • 函数指针
  • 指向指针的指针数组
  • 指向函数的指针数组
  • 。。。

指针的基本用法:

  1. 在函数间传递信息;若希望在被调函数中改变主调函数的变量,必须使用指针。
  2. 处理数组中的函数。
  3. 。。。

const的使用

  • 保护数组中的数据;若函数的意图不是修改数组中的数据,在函数原型和函数定义中声明形式参数时使用关键字const
    int sum(const int ar[], int n);  //函数原型
    
    int sum(const int ar[], int n)  //函数定义  
    {
        int i;
        int total;
    
        for (i = 0; i < n; i++)
        {
            total += ar[i];
        }
        return total;
    }
    
    • 上述代码中,const告诉编译器,该函数不能修改ar指向的数组中的内容,若在函数中不小心使用了类似ar[i]++的表达式,编译器会捕捉到错误。
    • 这样使用const可以保护数组的数据不被修改,该函数在处理数组时将其视为常量。
    • 若编写函数需要修改数组时,不用const;不用修改数组,使用const较好。

  • const创建变量const double PI = 3.14159; 虽#define指令也可创建类似功能的符号常量,但是const的用法更加灵活,可以创建const数组、const指针和指向const的指针。
const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};   //若程序尝试更改此数组元素的值,编译器将报错。  

//指向const的指针不能用于改变值:  
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};  
const double * pd = rates;    //pd指向数组的首元素;pd声明为const,表明,不能使用pd来更改它所指向的值:  
*pd = 29.89;  //不允许   
pd[2] = 222.11  //不允许   
/*  
无论使用指针表示还是数组表示法,都不允许使用pd修改它所指向数据的值;但可以让pd指向别处。  
pd++;  //让pd指向rates[1]---没问题  
指向const的指针通常用于函数形参中,表明该函数不会使用指针改变数据。  
eg:  
void show_array(const double *ar, int n);  
*/  

指针赋值和const使用的一些规则:(参见cpp p301)

  • 把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的:
  • 只能把非const数据的地址赋值给普通指针。(否则,通过指针就能改变const数组中的数据)
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};  
const double locked[4] = {0.08, 0.033, 0.044, 0.067};  

const double * pc = rates;  //有效  
pc = locked;   //有效   
pc = &rates[3];  //有效  

double * pnc = rates;  //有效  
pnc = locked;  //无效!!  
pnc = &rates[2];  //有效  

指针和多维数组

page 303

二维数组 int zippo[4][2] = {{2, 4}, {6, 8}, {1, 3}, {5, 7}};

特别注意:zippo[2][1]等价于 *(*(zippo + 2) + 1):

zippo                二维数组首元素的地址(每个元素都是内含两个int类型元素的一维数组)。   
zippo + 2            二维数组第3个元素(即,一维数组)的地址。
*(zippo + 2)         二维数组第3个元素(即,一维数组)的首元素(一个int类型值)地址。
*(zippo + 2) + 1     二维数组第3个元素(一维数组)的第2个元素(一个int类型值)地址。
*(*(zippo + 2) + 1)  二维数组第3个一维数组元素的第2个int类型元素的值,即数组第三行第二列的值zippo[3][2]

*(*(ptr_score + i) + j) <--> score[i][j] <--> *(*(score + i) + j)

zippo 是数组首元素的地址,zippo的首元素是一个内含两个int值的数组,所以zippo是这个内含两个int值的数组的地址。

声明一个指针变量pz,指向二维数组zippo:

int (* pz)[2];    //pz指向一个内含两个int类型值的数组  ; 等价于 int zippo[][2];
/*
声明pz为为指向一个数组的指针,该数组内含两个int类型值。使用`()`是因为,`[]`优先级高于`*`。
*/

int * pax[2];    //pax是一个内含两个指针元素的数组,每个元素都指向int的指针。
/*
[]优先级高,先与pax结合-->pax为一个内含两个元素的数组
*-->pax数组内含两个指针
int-->pax数组中的指针都指向int类型
*/

指针的兼容性:

  • 指针间赋值比数值类型之间要求更严格,指向不同类型的指针之间不能赋值。

声明一个指向N维数组的指针时,只能省略最左边一个括号中的值:

int sum2d(init ar[3][4], int rows);  //有效声明,但3将被忽略
int sum2d(int ar[][4], int rows);    //有效声明

int  sum4d(int[][12][20][30], int rows);  //ar是一个指针, ar指向一个12x20x30的int数组

变长数组(VLA)

符合字面量


关键概念:

  • 数组:用于储存相同类型的数据。c把数组看作是派生类型(因为数组是建立在其它类型基础上的—无法简地声明一个数组),声明数组时必须说明其元素的类型,eg:int类型,float类型,或其它类型,可以是数组类型—创建的是数组的数组(二维数组)
  • 通常编写一个函数来处理数组,在特定函数中解决特定问题,有助于实现程序模块化。
    • 在把数组名作为实际参数时,传递给函数的不是整个数组,而是数组的地址(函数对应的形式参数时指针)
    • 为处理数组,函数必须知道 从何处开始读取数据 和 要处理多少个元素;数组地址,提供了“地址”,“元素个数” 可以内置在函数中或作为单独的参数传递(更常用,可让函数处理不同大小的数组)。
  • 变长数组,可以用变量表示数组的大小。

小结:

  • c把数组名解释为该数组首元素的地址。—数组名与指向该数组首元素的指针等价。若ar为一个数组,则 ar[i]*(ar + i)等价。
  • 对c而言,不能将整个数组做为参数传递给函数,但可以传递数组的地址;函数使用传入的地址操控原始数据;若函数不修改原始数据,应在声明函数的形式参数时用关键字const;在被调函数中可以使用数组表示法或指针表示法(实际上使用的都是指针变量)。
  • 指针加上一个整数,或递增指针,指针的值以所指向对象的大小为单位改变。
  • 二维数组–数组的数组;double sales[5][12];:
    • 该数组名为 sales,有5个元素(一维数组),每个元素都内含12个double类型值的数组。
    • 第一个一维数组时sales[0],第二个一维数组时sales[1]…;每个元素都是内含12个double类型值的数组。
    • 使用第2个下标可以访问这些一维数组中的特定元素。eg:sales[2][5]sales[2]的第6个元素,而sales[2]sales的第3个元素。
  • c传递多维数组的传统方法是 把数组名(数组地址)传递给类型匹配的指针形参。声明这样的指针形参要指定所有的数组维度(除了第1个维度,传递第1个维度通常作为第2个形参)。eg:
void display(double ar[][12], int rows);
...
display(sales, 5);
  • 变长数组提供第2种语法,把数组维度作为参数传递。函数原型:
void display(int rows, int cols, double ar[rows][cols]);
...
display(5, 12, sales);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值