指针与地址
指针是能够存放一个地址的一组存储单元(通常是2个或4个字节)。
一元运算符&可用于取一个对象的地址,如p = &c; 表示将c的地址赋值给变量p,称p为“指向”c的指针。地址运算符&只能应用于内存中的对象,即变量与数组元素。
一元运算符*是间接寻址或间接引用运算符。当它作用于指针时,将访问指针所指向的对象。
int x = 1, y = 2, z[10];
int *ip; /* ip is a pointer to int */
ip = &x; /* ip now points to x */
y = *ip; /* y is now 1 */
*ip = 0; /* x is now 0 */
ip = &z[0]; /* ip now points to z[0] */
++*ip或(*ip)++ /* (*ip)++ 中的()是必需的,否则,该表达式将对ip进行加1运算,而不是对ip指向的对象进行加1运算,这是因为类似于*和++这样的运算符遵循从右至左的结合顺序 */
iq = ip /* 若iq是另一个指向整型的指针,则将ip中的值拷贝到iq中,这样,指针iq也将指向ip指向的对象 */
// 由于指针也是变量,所以在程序中可以直接使用,而不必通过间接引用的方法使用。
指针与函数参数
由于C语言是以值传递的方式将参数值传给被调用函数,因此,被调用函数不能直接修改主调函数中变量的值。
指针参数使得被调用函数能够访问和修改主调函数中对象的值。
// swap()的所有参数都声明为指针,并且通过这些指针来间接访问它们指向的操作数
void swap(int *px, int *py) {
int temp = *px;
*px = *py;
*py = temp;
}
// 使主调程序将所要交换的变量的指针传递给被调用函数
swap(&a, &b);
*语法糖*
// 由于一元运算符&用来取变量的地址,这样 &a 就是一个指向变量a的指针
void swap(int &a, int &b) {
int t = a;
a = b;
b = t;
}
// main()调用
swap(a, b);
指针与数组
通过数组下标能完成的任何操作都可以通过指针来实现。
但数组名和指针有一个不同之处,指针是一个变量,但数组名不是。所以,语句pa = a和pa++都是合法的;类似于a = pa和a++形式的语句是非法的。
int a[10]; // 定义一个长度为10的数组
int *pa; // 声明pa为指向整型对象的指针
pa = &a[0]; // 将指针pa指向数组a的第0个元素,即pa的值为数组元素a[0]的地址
pa = a; // 因为数组名所代表的就是该数组最开始的一个元素的地址,通常取代pa = &a[0];
x = *pa; // 将数组元素a[0]的内容复制到变量x中
pa[i]与*(pa+i)是等价的 // 一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现
对数组元素a[i]的引用也可以写成*(a+i)
&a[i]和a+i的含义相同【&a[0]等价于a】 // a+i是a之后第i个元素的地址
【若指针pa指向a[0],则*(pa+1)引用的是a[1]的内容,
pa+i是a[i]的地址,*(pa+i)引用的是a[i]的内容
“指针加1”意味着pa+1指向pa所指向的对象的下一个对象;pa+i指向pa所指向的对象之后的第i个对象】
当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。
在被调用函数中,该参数是一个局部变量,因此数组名参数必须是一个指针,也就是一个存储地址值的变量
/* strlen: return length of string s */
int strlen(char *s) {
int n;
for (n = 0; *s != '\0', s++) {
n++;
}
return n;
}
// 因为s是一个指针,所以对其执行自增运算是合法的。执行s++运算不会影响到strlen函数的调用者中的字符串,它仅对该指针在strlen函数中的私有副本进行自增运算
地址算数运算
指针算术运算的特点:
1. 在某些情况下对指针可以进行比较运算
指针p和q指向同一个数组的成员,若p指向的数组元素的位置在q指向的数组元素位置之前,则关系表达式
p < q
的值为真
2. 指针可以和整数进行相加或相减运算
(1) 结构 p + n 表示指针p当前指向的对象之后第n个对象的地址
在计算p+n时,n根据p指向的对象的长度按比例缩放(p指向的对象的长度取决于p的声明)
(2) 如果指针p和q指向同一个数组中的元素,且p < q,
那么 q - p + 1 就是位于p和q指向的元素之间的元素的数目。
/* strlen: return length of string s */
int strlen(char *s) {
char *p = s;
while (*p != '\0') {
p++;
}
return p - s;
}
(3) 指针的算术运算具有一致性 ???留疑
有效的指针运算包括同类型指针之间的赋值运算;
指针同整数之间的加法或减法运算;
指向相同数组中元素的两个指针间的减法或比较运算;
将指针赋值为0或指针与0之间的比较运算。
其他所有形式的指针运算都是非法的
字符指针与函数
字符串常量是一个字符数组,例如:"I am a string"。在字符串的内部表示中,字符数组以'\0'结尾,所以,程序可以通过检查空字符找到字符数组的结尾。字符串常量占据的存储单元数比双引号内的字符数大1。
字符串常量的用法:
1. 作为函数参数
printf("Hello world\n");
当类似于这样一个字符串出现在程序中时,实际上是通过字符指针访问该字符串的。
在上述语句中,printf接受的是一个指向字符数组第一个字符的指针,即字符串常量可通过一个指向其第一个元素的指针访问
由于++和--既可以作为前缀运算符,也可以作为后缀运算符,所以还可以将运算符*与++和--按照
其他方式组合使用。例如,表达式 *--p 在读取指针p指向的字符之前先对执行自减运算。
// 下面两个表达式是进栈和出栈的标准用法
*p++ = val; /* 将val压入栈 */
val = *--p; /* 将栈顶元素弹到val中 */
指针数组以及指向指针的指针
由于指针也是变量,所以它们也可以像其他变量一样存储在数组中。
使用场景:如编写程序按字母顺序对由文本行组成的集合进行排序【每行文本行长度不一】(与整数不同的是,它们不能在单个运算中完成比较或移动操作)。需要一个能够高效,方便地处理可变长度文本行的数据表示方法。
这种实现方法消除了因移动文本行本身所带来的复杂的存储管理和巨大的开销这两个孪生问题。
</br>
多维数组
C语言提供了类似于矩阵的多维数组,但实际上它们并不像指针数组使用得那么广泛。
使用场景:如日期转换问题。
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
/* day_of_year: set day of year from month & day */
int day_of_year(int year, int month, int day) {
int i, leap;
leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
for (i = 1; i < month; i++)
day += daytab[leap][i];
return day;
}
/* month_day: set month, day from day of year */
void month_day(int year, int yearday, int *pmonth, int *pday) {
int i, leap;
leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
for (i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i];
*pmonth = i;
*pday = yearday;
}
</br>
指针数组的初始化
</br>
指针与多维数组
二维数组与指针数组之间的区别
假如有以下两个定义:
int a[10][20];
int *b[10];
指针数组最频繁的用处是存放具有不同长度的字符串。
以函数month_name中的情况为例作比较:
1. 指针数组的声明和图形化描述
char *name[]={"Illegal manth", "Jan", "Feb", "Mar"};
2. 二维数组的声明和图形化描述
char aname[][15] = { "Illegal month", "Jan", "Feb", "Mar" };
命令行参数
指向函数的指针,复杂声明等章节略过