C和指针(一)


学习这部分内容的初衷是希望详细了解函数指针、动态内存分配两部分内容。关于数据结构的学习记录在另一篇博文中。大一时便接触C语言基础内容,之后的几年里断断续续看过一些相关的书籍,但一直都处于门外徘徊的层面。进入研究生阶段,参与实验室项目时偏向于软件,Labview、C#、VB、Matlab都有接触,甚至试图看过一段时间的MFC(由于难度较大放弃了),单片机、嵌入式系统、PLC都是接下来计划学习的内容。不论使用何种语言,其中都隐含着C的影子,或者说,通过C语言学习编程的技巧、方法、思想最合适不过了。

NULL指针,作为一个特殊的指针变量,表示不指向任何东西。例如,函数返回NULL指针。
1、指针的指针:
int a=12;
int *b=&a; //b equal to &a; *b equal to a, 12.
int **c=&b; //c equal to &b; *c equal to b, &a; **c equal to a, 12.
2、指针表达式
首先明确左值、右值的概念。
char ch='a';
char *cp=&ch;
表达式形式如下:
C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

C和指针(一)

3、指针运算
算数运算:
指针+/-整数,标准定义这种形式只能用于指向数组中某个元素的指针。
指针 - 指针,只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。相减的结果类型是ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离。
假定下图中数组元素的类型为float,每个元素占据4个字节的内存空间,如果数组的起始位置为1000,p1的值是1004,p2的值为1024,但表达式p2-p1的值将是5,因为两个指针的差值(20)将除以每个元素的长度。
C和指针(一)
关系运算:
<   <=   >   >=   前提是它们都指向同一个数组中的元素。
例:for( vp = &values[N_VALUES - 1]; vp >= &values[0]; vp-- )
*vp = 0;
该例子将出现错误,问题所在:比较表达式vp >= &values[0] 的值是未定义的,因为vp移到了数组的边界之外。标准允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置指针进行比较,但是不允许与指针第一个元素之前的那个内存位置的指针进行比较。
注意:越界指针和指向未知值的指针式两个常见的错误根源。

4、函数
C语言的规则很简单,所有参数都是传值调用。指针、数组等形式执行间接访问操作。
函数中,在声明数组参数时不指定它的长度是合法的,因为函数并没有为数组元素分配内存。
ADT和黑盒:
C语言可以设计和实现抽象数据类型(ADT,abstract data type),因为它可以限制函数和数据定义的作用域,这种技巧也被称作黑盒(black box)设计。抽象数据类型的基本想法是很简单的——模块有功能说明和接口说明,前者说明模块所执行的任务,后者定义模块的使用。限制对模块的访问是通过static关键字的合理使用实现的,它可以限制对那些非接口的函数和数据的访问。
(不是很理解,之后再完善)
递归:
C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。
(先略过)
可变参数列表
可以让一个函数在不同的情况下接受不同数目的参数。
stdarg宏:可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件中,它是标准库的一部分。这个头文件声明了一个类型 va_lis t和三个宏—— va_start、va_arg 和 va_end。我们可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。例程中,注意参数列表中的省略号,它表示此处可能传递数量和类型未确定的参数。
可变参数的限制:可变参数必须从头到尾按照顺序逐个访问。所有作为可变参数传递给函数的值都将执行缺省参数类型提升。
float average( int n_values, ... )
{
va_list var_arg;
int count;
float sum = 0;

//准备访问可变参数
va_start( var_arg, n_values );

//添加取自可变参数列表的值。
for( count = 0; count < n_values; count += 1 ){
sum += va_arg( var_arg, int );
}

//完成处理可变参数
va_end( var_arg );
return sum / n_values;
}

参数列表中至少有一个命名参数。通过命名参数,指定可变参数的数量、类型等信息。

5、数组
数组名的值是一个指针常量。只有在两种场合下,数组名并不用指针常量来表示。当数组名作为sizeof操作符或单目操作符&的操作数时。如,sizeof(a)返回数组的长度,而不是指向数组的指针的长度;&a[0]是一个指向数组第1个元素的指针。
int a[10];
int b[10];
int *c;
c = &a[0];
c = a; //c指向数组a的第一个元素
b = a; //语句是非法的。不能用赋值符把一个数组的所有元素复制到另一个数组。必须使用循环。
a = c; //赋值是非法的,在这个表达式中,a的值是个常量,不能被修改。
指针、数组与下标:c     c+1     *c+1     *(c+1)    
(指针与数组下标的效率问题)
数组与指针:数组与指针并不是相等的。
int a[5];
int *b;
声明数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值为一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存。而且,指针变量并未被初始化为指向任何现有的内存空间,不会被自动初始化。
C和指针(一)
因此,上述声明后,表达式*a是完全合法的,但表达式*b却是非法的。另一方面,b++可以通过编译,但a++却不行。
静态和自动初始化
多维数组:

指向数组的指针:
int vector[10], *vp = vector;
int matrix[3][10], *vm = matrix;
其中第一个声明合法。它为一个整型数组分配内存,并把vp声明为一个指向整型的指针,把vp初始化为指向vector数组的第一个元素;第二个声明,对vm的初始化不合法。mp声明为一个指向整型的指针。但是,对mp的初始化是不正确的,因为matrix并不是一个指向整型的指针,而是一个指 向整型数组的指针
声明一个指向整型数组的指针:
int (*p)[10];
p指向某类型(整型)的数组,数组中有若干(10个)元素。
声明并初始化:
int (*p)[10] = matrix;
则p指向数组matrix的第一行。即p是一个指向拥有10个整型元素的数组的指针。当执行p++等操作时,p指向数组的下一行,指针在数组中逐行移动。(系统会自动根据数组列数为10,调整指针的值)
如果希望指针逐个访问整型元素而不是逐行在数组中移动,应该定义如下:
int *pi = &matrix[0][0];
int *pi = matrix[0];

作为函数参数的多维数组:
作为函数参数的多维数组名的传递方式和一维数组名相同——实际传递的是个指向数组第1个元素的指针。但是两者的区别在于,多维数组的每个元素本身是另一个数组,编译器需要知道它的维数,以便为函数形参的下标表达式进行求值。(编译器认为多维数组是包含有N个(行)数组的一维数组(列))
int vector[10];
...
func1(vector);
参数vector的类型是指向整型的指针,所以func1的原型可以是下面两种中的任何一种:
void func1( int *vec );
void func1( int vec[] );
作用于vec上面的指针运算把整型的长度作为它的调整因子。
而:
int matrix[3][10];
...
func2( matrix );
这里,参数matrix的类型是指向包含10个整型元素的数组的指针。func2的原型应为:
void func2( int (*mat )[10] );
void func2( int mat[][10] );
在这个函数中,mat的第一个下标根据包含10个元素的整型数组的长度进行调节,接着第2个下标根据整型的长度进行调节,这和原先的matrix数组一样。(在func2中,mat++操作指针在数组中逐行移动)

在多维数组中,只有第1维才能根据初始化列表缺省地提供。剩余的几个维必须显式地写出,这样编译器就能推断出每个子数组维数的长度。

指针数组:
int *api[10];
(8.3内容)

6、字符
(第9章内容)



一、《C和指针》学习笔记
1章
在C语言中,数组参数是以引用形式进行传递的,也就是传址调用。
2章
编译、链接与执行
3数据
C语言中不存在字符串类型。C语言存在字符串的概念,通常存储在字符数组中,这也是C语言没有显式字符串类型的原因。
字符串常量的直接值是一个指针,而不是这些字符本身。在程序中使用字符串常量会生成一个“指向字符串的常量指针“,当一个字符串常量出现于一个表达式中时,表达式所使用的值就是这些字符所存储的地址,而不是这些字符串本身。因此,你可以把字符串常量赋值给一个”指向字符的指针“,后者指向这些字符所存储的地址。但是,不可以把字符常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。
例:

typedef:允许为各种数据类型定义新名字。
typedef char *ptr_to_char; //把标示符ptr_to_char作为指向字符的指针类型的新名字。
ptr_to_char a; //a便是一个指向字符的指针
使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明。typedef在结构中特别有用。
const常量:
在函数中声明为const的形参在函数被调用时会得到实参的值。
当涉及指针变量时,情况变得更加有趣,因为有两样东西都有可能成为常量——指针变量和它所指向的实体。下面是几个声明的例子:
int *pi; //pi为指针
int const *pci; //指向整型常量的指针。可以修改指针的值,但不可以修改它所指向的值
int * const cpi; //指针是常量,它的值无法修改,但可以修改它所指向的整形的值
int const * const cpci; //无论指针还是它所指向的值都是常量,不允许修改。
链接属性:internal、external、(none)
没有链接属性的标识符总是被当做单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。属于internal链接属性的标识符在同一个源文件内所有的声明都指向同一个实体,但位于不同源文件的多个声明则分属于不同的实体。属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。
例:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值