一、指针基础
以int c = 0;
为例
变量三要素:变量名c
,值0
,和其在内存中的地址。
指针即地址值
指针变量为专门存放指针(某个变量的地址)的变量。
或:指针变量的值为另一个变量的地址。
指针的大小一般为4字节(sizeof(指针变量) == 4
),64位编译器中则为8。
定义一个指针变量:int *pointer;
其中
-
int
说明了指针变量的基类型 -
*
为指针运算符,说明pointer
的类型为指针 -
pointer
为指针变量名
基类型:指针变量指向的变量的类型
int c = 76;
int *pointer; //定义名字为pointer的指针变量
pointer = &c; //将变量c的地址赋值给指针变量pointer,称指针变量pointer指向了变量c
int *p1 = NULL
指针变量赋初值,使之成为空指针。
&
*
运算符的优先级
从高到低分别为:
- 后置
++
--
- 前置
++
--
,!
*
&
- 算数运算符
- 关系运算符
<=
<
>=
>
==
!=
-
&&
||
- 赋值运算符
=
注意:(*pointer)++
不等于*pointer++
前者指*pointer
的值加1,后者指*pointer
使用后,地址加一,即指向下一个地址单位的存储的变量,具体加几个字节由pointer
的基类型决定。
二、指针与数组
1.指针指向数组元素时与指向普通变量没有区别
2.与数组
int a[5] = {10, 11, 12, 13, 14};
cout<< a<<endl; //0017F754
cout<<*a<<endl; //10
cout<<&a[0]<<endl; //0017F754
cout<<a[0]; //10
可以看出:
数组名代表数组首元素的地址,数组名相当于 (并不是)指向数组首元素的指针。因此,数组名不是变量,不能给数组名赋值。
若定义:数组
int a[10]
, 指针int *pointer;
则pointer = a;
等价于pointer = *a[0];
数组访问:
pointer + i;
等价于a + i;
等价于&a[i];
*(pointer + i);
等价于*(a+i);
等价于a[i];
表示形式:
pointer[i];
等价于*(pointer + i);
注意:
-
a++
是没有意义的,但是p++
会引起p的变化 - p可以指向数组之外的元素
- 因此,指针做加减运算时一定注意有效范围
3.与二维数组
例:输入i, j
,输出a[i][j]
int a[3][4] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23};
int (*p)[4], i, j; //p指向一个“包含4个int型元素的一维数组”
p = a;
cin >> i >> j;
cout << setw(4) << *(*(p + i) + j);
因为p指向一个“包含4个int型元素的一维数组”
所以*(p + i)
指向第i + 1个“包含4个int型元素的一维数组”
p + i
等价于&a[i]
*(p + i)
等价于a[i]
,即指向第i行
*(p + i) + j
等价于a[i] + j
,即指向第j列
** *(*(p + i) + j)
便等价于a[i][j]
**
根据c语言的规范,此处直接写p[i][j]
也可以
三、指针与字符串
指向字符串的指针:
char a[10]; char *p; p = a;
字符串与数组的区别:字符串末尾一定为\0
字符串为双引号包含:"hello"
,系统会自动补\0
。数组为大括号内{'h', 'e', 'l', 'l', 'l', '\0'}
(不加\0
的话就没有)。
与整形数组的区别:
打印指向字符串数组的指针,会打印整个字符串,而不是地址。
打印指向整形数组的指针,则会打印地址。
int a[5] = {1, 2, 3};
int *pa = a;
char b[6] = {'h', 'e', 'l', 'l', 'l', '\0'};
char *pb = b;
//同样是打印数组名或者指针
cout<<a<<endl; //整形数组打印a的地址值
cout<<pa<<endl; //打印a的地址值
cout<<b<<endl; //字符串打印内容hello
cout<<pb<<endl; //打印hello
char buffer[10] = "ABC";
char *pc;
pc = "hello";
cout<<pc<<endl; //打印hello
pc++;
cout<<pc<<endl; //打印ello
cout<<*pc<<endl; //打印e
pc = buffer; //实际是把buffer[]的首地址赋给了pc,因此pc也指向了这个字符串
cout<<pc; //打印ABC
四、指向二维数组的指针
1.再谈指向一维数组的指针
p -> a[0]
则++p -> a[1]
由基类型起作用
根据c语言规范,一个数组名,当它不作为
&
,sizeof
,Alignof
的操作数时,可以看作是指向数组首的指针变量。
a是一个数组名,则a可以看作指向首元素的指针,其基类型为a数组的类型。&a则是指向整个数组的地址值,可以看作指向整个数组的指针,其基类型为大小、类型与a相同的数组。&a
相当于把a的管辖范围上升了一级,从首元素的地址上升到了整个数组的地址。*a
则可以把管辖范围下降一级,从首元素的地址下降到首元素的值。因此*(&a)
相当于,先上升,再下降,最后结果即为:a
。因此cout<<*(&a)
打印的结果与cout<<a
相同,都打印数组a首元素的地址值。
2.指向二维数组的指针
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
二位数组a[3][4]
包含三个元素:a[0]
,a[1]
,a[2]
,每一个元素都是包含四个整形元素的一维数组。
按级别:
名称 | 级别 | 含义 |
---|---|---|
&a | 最高 | 指向整个二维数组的指针 |
a | 高 | 指向a[0]即第一个一维数组的指针 |
a[0] | 低 | 指向第一个一维数组中第一个元素的指针 |
a[0][0] | 最低 | 第一个一维数组中第一个元素的值 |
同时可以使用&
和*
运算符对他们进行级别的提升和下降;
三条规律
- 数组名相当于指向数组第一个元素的指针
-
&
可以提升一个级别 -
*
可以下降一个级别
五、指针与函数
1.指针做函数的参数
可以将数组名作为实参传递给函数的指针形参
多维数组名作函数实参
int maxvalue( int (*p)[4]); //p[i][j]即可访问数组a内的内容
main函数里:
int a[3][4];
数组名也可以直接做形参。C++编译器会将形参数组名作为指针变量来处理。
int sum(int arr[][4]);
与int sum((*arr)[4]);
等效。前者:arr[]
是形参,会被解释为arr
指针变量,[4]
说明arr
指向的是有4个int型元素的数组。
int sum(int array[], int n); //合法,array被当作了指针变量这样能够改动main函数里变量的值,可能会导致未知的危险
int sum(const int array[], int n); //合法,且函数内*array的值不能被更改
指向符号常量的指针const int *p
。不能通过*p = 20
来赋值,或者其他方式修改指向的内容,即使它指向的变量不是常量。
2.指针做函数的返回值
int *function(int x, int y);
注意,应返回全局变量或静态局部变量的地址,否则子函数结束后,内存被释放,这个地址就没有意义了
静态局部变量
在子函数中定义,定义方式为static int value1 = 10
。函数执行完毕后,内存被释放,但是这个静态局部变量不会被释放,因此仍然可以被使用。假设函数执行完毕后value1 == 20;
,下一次执行此函数时,虽然按理又要执行定义语句static int value1 = 10
使value1变成10,实际value1的值仍未20,并不会被再次初始化。
虽然子函数会被调用多次,但是静态局部变量的定义语句只会执行一次。
六、练习
1.计算矩阵边缘元素之和
输入:
1 //数据组数
4 4 //矩阵行数、列数
1 1 1 1
0 0 0 0
1 0 1 0
0 0 0 0
3 3
3 4 1
3 7 1
2 0 1
输出:
5
15
代码:
#include<stdio.h>
#include<iostream>
using namespace std;
int answer;
int compute()
{
int m, n;
cin >> m >> n;
int answer = 0;
for(int i = 0; i < m; ++i)
{
int *p = new int[n];
for(int x = 0; x < n; ++x)cin>>*(p + x);
if(i == 0 || i == m - 1)
for(int j = 0; j < n; ++j)
{
answer += *(p + j);
}
else
{
answer = answer + *p + *(p + n - 1);
}
}
return answer;
}
int main()
{
int k = 0;
cin >> k;
int *p = new int[k];
for(int i = 0; i < k; ++i)
{
*(p + i) = compute();
}
for(int i = 0; i < k; ++i)
{
cout<<*(p + i)<<endl;
}
return 0;
}
2.二维数组右上左下遍历
输入:
3 4 //矩阵行数列数
1 2 4 7
3 5 8 10
6 9 11 12
输出:
1
2
3
4
5
6
7
8
9
10
11
12
代码:
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
int row, col;
cin>>row>>col; //row == 3, col == 4
int *p = new int[col * row];
for(int i = 0; i < row; ++i)
{
for(int j = 0; j < col; ++j)
cin>>*(p + i * col + j);
}
for(int j = 0; j < col; ++j)
{
cout<<*(p + j)<<endl;
for(int i = 1, k = j - 1; k >= 0 && i < row; --k, ++i)
cout<<*(p + i * col + k)<<endl;
}
for(int i = 1; i <= row - 1; ++i)
{
cout<<*(p + i * col + col -1)<<endl;
for(int j = col - 2, k = i + 1;j >= 0 && k < row;++k, --j)
cout<<*(p + k * col + j)<<endl;
}
return 0;
}
3.文字排版
输入:
84 //单词数
One sweltering day, I was scooping ice cream into cones and told my four children they could "buy" a cone from me for a hug. Almost immediately, the kids lined up to make their purchases. The three youngest each gave me a quick hug, grabbed their cones and raced back outside. But when my teenage son at the end of the line finally got his turn to "buy" his ice cream, he gave me two hugs. "Keep the changes," he said with a smile.
输出:
One sweltering day, I was scooping ice cream into cones and told my four
children they could "buy" a cone from me for a hug. Almost immediately, the kids
lined up to make their purchases. The three youngest each gave me a quick hug,
grabbed their cones and raced back outside. But when my teenage son at the end
of the line finally got his turn to "buy" his ice cream, he gave me two hugs.
"Keep the changes," he said with a smile.
代码:
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
int lengthOfstr(char *str)
{
return strlen(str);
}
int main()
{
char str[50];
int num, lengthOfLine = 0;
cin>>num;
for(int i = 0; i < num; ++i)
{
cin>>str;
if(i == 0) //全文第一个单词
{
cout<<str;
lengthOfLine += strlen(str);
}
else if(lengthOfLine + 1 + strlen(str) <= 80) //不是第一个单词且输出这一个单词后行字符数没超过80
{
cout<<" "<<str;
lengthOfLine = lengthOfLine + 1 + strlen(str);
}
else if(lengthOfLine + 1 + strlen(str) > 80) //不是第一个单词且输出这一个单词后行字符数将超过80
{
cout<<endl<<str;
lengthOfLine = strlen(str);
}
}
return 0;
}