简介
地址和指针:
- 内存区的每一个字节有一个编号,这就是地址。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元.
- 知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此,又把变量的地址称为该变量的指针
- 在C语言中,对变量的访问有两种方式,直接访问和间接访问
- 在C语言中,指针是一种特殊的变量,他是存放地址的,假设我们定义了一个指针变量 int *i_pointer用来存放整型变量i的地址。可以通过语句i_pointer = &i ;
- *为取值操作符
&为取址操作符
如
#include <stdio.h>
int main() {
int i = 2000;
int *pointer ;
pointer = &i;
printf("%d\n", *pointer);
return 0;
}
//output:2000
知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此,又把变量地址称为该变量的‘指针’,C语言中可以专门定义这一类的变量,称为指针变量,但是指针变量的值(即指针变量中存放的值)是地址(即指针)。
定义一个指针变量 *
下面都是合法的定义
float *pointer_3;//pointer_3是指向float型变量的指针变量
char *pointer_4;//pointer_4是指向字符型变量的指针变量
//可以用赋值语句使一个指针变量得到另一个变量的地址,从而使他指向一个该变量
其中,*代表一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。
例如:
float *pointer_1;
指针变量名pointer_1,而不是 *pointer_1
在定义指针变量时必须指定基类型
1.只有整形变量的地址才能放到指向整形变量的指针变量中
比如
float i = 2000.0;
int *pointer ;
pointer = &i;
//这样就错了
2.指针变量的引用‘&’
请牢记,指针变量中只能存放地址,不要将一个整数(或任何其他非地址类型的数据)赋值给一个指针变量,否则编译器也会把它当做一个地址处理
- &*pointer_1的含义是什么?
&和 * 的运算符 的优先级别相同,但是按照自右而左方向结合,因此先进行 *pointer_1的运算,他就是变量a,在执行&运算
如果有
pointer_1 = &a;
pointer_2 = &*pointer_1;
他的作用是将&a(a的地址)赋值给了pointer_2,如果pointer_2原来指向了b,经过重新赋值后它已经不再指向
b了,而指向了a。
- *&a的含义是什么?
先进行&a运算,得到a的地址,再进行 *运算。即&a所指向的变量,也就是变量a。
*&a和 *pointer_1的作用是一样的,他们都等价于变量a。即 *&a与a等价。 - (*pointer_1)++相当于a++
注意括号是非常重要的,如果没有括号,就成为了 *pointer_1++,从附录可知:++和 为同一优先级别,而结合方向为自右而左,因此他相当于(pointer_1++)
由于++在pointer_1的右侧,是‘后加’,因此先对pointer_1的原值进行 *运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。
现在写一个简单的小案例,输入a,b两个数,并且从小到大输出
void exchange(){
int a,b,*p1,*p2,*p;
scanf("%d %d", &a, &b);
if(a>b){
p1 = &a;
p2 = &b;
p = p1;
p1 = p2;
p2 = p;
}
printf("%d,%d", *p1, *p2);
}
然后再试着将两个数通过传参传进来再进行交换
void swap(int *p1,int *p2){//交换传入的数字
int *p;
if(*p1>*p2){
p = p1;
p1 = p2;
p2 = p;
}
printf("%d %d",*p1,*p2);
}
int Scanf(){//输入数字的函数
int a,b,*p1,*p2;
scanf("%d %d", &a,&b);
p1=&a;
p2=&b;
swap(p1,p2);
}
int main() {
Scanf();
return 0;
}
接下来我们来进行优点难度的程序练习,输入三个数字,然后从小到大排序
void swap2(int *p1, int *p2, int *p3) {//将三个数按照从小到大输出
if(*p1>*p2){
swap3(p1,p2);
}if(*p1>*p3) {
swap3(p1,p3);
}if(*p2>*p3){
swap3(p2,p3);
}
}
void swap3(int *p1, int *p2){//置换
int term;
term = *p1;
*p1 = *p2;
*p2 = term;
}
int main() {
Scanf2();
return 0;
}
一维数组指针
- 一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。
- 指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)
- 所谓数组元素的指针就是数组元素的地址
例如
int a[10];
int *p;
p=&a[0];
//意思就是把a[0]元素的地址赋给指针变量p
也就是把p指向的a的第0号元素
引用一个数组元素
下标法:a[i]
指针法:*(a+i)
这里的a也就是我们所说的数组名,而数组名是什么呢,数组名就是数组第一个元素的地址
下面做一个简单的测试
输出指定数组中的全部元素
void test();
void test(){//指针输出指定数组的元素
int a[5]={1,2,3,4,5};
for (int i = 0; i < 5; ++i) {
printf("%d",*(a+i));
}
}
int main(){
test();
return 0;
}
同时我们也可以采用另一种方法
void test1(){
int a[5]={1,2,3,4,5};
int *p;
for (p = a; p < (a+5); ++p) {
printf("%d",*p);
}
}
当我们作为函数参数传递一个数组的时候通常都会这么写f(int array[],int n),但是实际上在编译的时候array是按照指针变量处理的,相当于将函数f的首部写成了f(int *array),以上两种方法是等价的。
下面我们将数组arr中n个整数,按照倒序存放
void test1(){
int a[6]={1,2,3,4,5,6};
int *p;
reverse(a,6);
for (p = a; p < (a+6); ++p) {
printf("%d",*p);
}
}
void reverse(int *a,int n){
int midNum = (n - 1) / 2;
int *i;
int *j;
int *p;
i=a;
j=a+n-1;
p=a+midNum;
int term;
for (; i <= p; ++i,j--) {
term = *i;
*i = *j;
*j = term;
}
}
int main(){
test1();
return 0;
}
然后再来尝试找出多个数中的最小值和最大值
void peak(){
int a[6]={1,2,3,4,5,6};
int *p,*max,*min;
int i = 0;
p=a;
max = a;
min = max;
for (; p < (a + 6); ++i,++p) {
if(max<a+i){
max = a+i;
}if (a+i<min){
min = a+i;
}
}
printf("%d,%d", *max, *min);
}
int main(){
peak();
return 0;
}
二维数组指针
- 先回顾一下多维数组的性质,可以认为二维数组是‘数组的数组’,例如定义
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
则二维数组a是由三个一维数组所组成的。设二维数组的首行首地址为2000,则有…
通过表格,然后来写一个案例程序
void test2(){
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("a:%d\n",a);
printf("*a:%d\n", *a);
printf("*(a[0]+1):%d\n", *(a[0] + 1));
printf("*(*(a+1)+2):%d\n", *(*(a + 1) + 2));
}
int main(){
test2();
return 0;
}
/**output:
*a:6422176
*a:6422176
*(a[0]+1):2
*(*(a+1)+2):7
*
*/
- 指向多维数组的指针变量:
把二维数组a分解成一维数组a[0],a[1],a[2]之后,设p为指向二维数组的指针变量。可定义为:
int(*p)[4],他表示的是个指针变量,指向包含4个元素的一维数组。若指向第一个一维数组a[0],其值等于a,a[0],或&a[0][0],而p+i则指向一维数组a[i]。
二维数组指针变量说明的一般形式为:
类型说明符(*指针变量名)[长度]
其中,
类型说明符:所指数组的数据类型
*:其后的变量是指针类型
长度:表示二维数组分解成多个一维数组的时候,一维数组的长度就是二维数组的列数
接下来来写两个案例
用指针变量来输出二维数组元素的值
void pointerCollection(){//用指针变量输出二维数组的值
int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
int (*p)[4];
p = a;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; ++j) {
printf("%d\t", *(*(p + i) + j));
}
}
}
int main(){
pointerCollection();
return 0;
}
用指针变量,输入指定的行数和列数,打印指定的任意行或列
void pointTest(){//输入指定的行数和列数,打印指定的任意行或列
int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
int num;
int (*p)[4];
p = a;
printf("查看行(1),列(2),请问查看哪一个,输入数字");
scanf("%d", &num);
switch (num){
case 1:
printf("那是第几行\n");
int line;
scanf("%d", &line);
for (int i = 0; i < 4; ++i) {
printf("%2d\t",*(*(p+0)+i));
}
break;
case 2:
printf("那是第几列");
int list;
scanf("%d", &list);
for (int i = 0; i < 3; ++i) {
printf("%2d\t",*(*(p+i)+0));
}
break;
}
}
int main(){
pointTest();
return 0;
}
字符数组、字符指针
在正式开始说字符数组指针之前,先来写一个简单的程序来体现一下字符数组
void test();
void test(){
char string[] = "I love China!";
printf("%s", string);
}
int main(){
test();
return 0;
}
然后我们用字符指针来重新写一遍刚才的代码
void test1(){
char *string = "I love China!";
printf("%s", string);
}
int main(){
test1();
return 0;
}
字符串中字符的读取方法
- 对字符串中字符的存取,可以用下标方法,也可以用指针方法
下标法举例:将字符串a复制为字符串b
void copy1(){
char string[] = "I like China!";
int i;
char stringcopy[20];
for (i = 0;*(string+i)!='\0'; i++) {
*(stringcopy + i) = *(string + i);
}
*(stringcopy + i) = '\0';
for (int j = 0; *(stringcopy+j)!='\0'; ++j) {
printf("%c",stringcopy[j]);
}
}
int main(){
copy1();
return 0;
}
指针法举例:将字符串a复制为字符串b
void copy2(){
char string[] = "I like China!";
char stringcopy[20];
char *p, *pc;
p = string;
pc = stringcopy;
for (; *pc != '\0'; p++, pc++) {
*pc = *p;
}
*pc = '\0';
for (int i = 0; *(stringcopy+i)!='\0'; ++i) {
printf("%c", stringcopy[i]);
}
}
int main(){
copy2();
return 0;
}
基本的知识点都说完了,下面我们来试一下用函数来调用实现字符串的复制
(1)用字符数组做形参。
void copy3(char x[],char y[]){
for (int i = 0;x[i]!='\0'; ++i) {
*(y + i) = *(x + i);
}
for (int j = 0; y[j] !='\0'; ++j) {
printf("%c", y[j]);
}
}
int main(){
char a[] = "I love China!";
char b[20];
copy3(a, b);
return 0;
}
(2)用字符指针变量做形参。
void copy4(char *a,char *b){//字符指针进行复制
for (; *a != '\0'; a++, b++) {
*b = *a;
}
*b = '\0';
}
int main(){
char *a = "I love China!";
char b[20];
copy4(a, b);
printf("%s\n%s\n", a, b);
return 0;
}
不过在这里要保留一个疑问,为什么在形参进行语句输出,控制台显示为空?
.
.
.
.
.
- 最后对字符指针和字符数组进行一个总结
虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但他们两者之间是有区别的,不应混有一谈
主要概括下来有以下几点:
1.字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址),决不是将字符串放到字符指针变量中。
用赋值方法进行举例:
字符数组:
char a[10] = “xxxx”;
字符指针:
char *a = “xxxx”;
区别在于字符指针赋值给a的不是字符,而是首地址。
2.如果定义了一个字符数组,在编译的时候为他分配内存单元,它有确定的地址。而定义一个字符指针变量的时候,给指针变量分配内存单元,在其中可以放一个字符变量的地址。
如:
char str[10];
scanf("%s,&str);
是完全可以的,但是如果使用如下方法,就会很危险
char *str;
scanf("%s",a);
3.指针变量是可以改变的void change(){//改变指针变量的值 char *a = "I love China"; printf("%s\n", a); a = a + 7; printf("%s", a); } int main(){ change(); return 0; } //output: //I love China //China
另外需要说明的就是,如果定义了一个指针变量,并且让他指向一个字符串,就可以用下标形式引用指针变量所指的字符串中的字符
例如:
char *a = “I love China”;
那么a[0]就是“I”
指向函数的指针
可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译的时候被分配给了一个入口地址。这个函数的入口地址就是函数的指针。
先写一个简单的函数指针案例
int max(int a,int b){
return a>b?a:b;
}
int main(){
int a,b,c;
int (*p)();
scanf("%d%d", &a, &b);
p = max;
c = (*p)(a,b);
printf("最大值是%d", c);
return 0;
}
使用函数指针表面上看上去写法要比以前的写法还要麻烦,实际上函数指针多用于在被调用的形参中使用实参函数。
例如
void sub(int (*p1)(int),int (*p2)(int, int)){
int a,b,i,j;
a = (*p1)(i);//调用引入的第一个函数
b = (*p2)(i, j);//调用引入的第二个函数
}
由此可见,函数指针的作用就是在形参内通过指针变量调用其他函数,而对于sub函数来说,形参就是p1,p2,实参就是这两个函数指针指向的函数,意思都是一样的,给函数指针传递的也是函数的地址。
下面做一个练习
设计一个process函数,调用的时候,每次实现不同的功能
传入两个int类型的参数ab,第一次实现max(a,b),第二次实现min(a,b)
void process(int a, int b, int (*p)()){
int c;
if (p==maxNum){
c = maxNum(a, b);
printf("%d", c);
}else if (p==minNum){
c = minNum(a, b);
printf("%d", c);
}
}
int minNum(int a,int b){
return a > b ? b : a;
}
int maxNum(int a,int b){
return a > b ? a : b;
}
int main(){
int a,b;
scanf("%d%d", &a, &b);
printf("求最大值输入1,求最小值输入2");
int c;
scanf("%d", &c);
switch (c){
case 1:
process(a,b,maxNum);
break;
case 2:
process(a,b,minNum);
break;
}
return 0;
}
把指针作为返回值
一个函数可以带回字面量,也可以带回指针型的数据,也就是地址,其概念与之前类似,只是带回的类型是指针类型,这种带回指针值的函数,一般定义形式为
类型名 *函数名(参数列表)
例如
int *a(int x, int y);
实例程序:
有若干学生的成绩,每个学生有四门课程,要求输出学生id之后,能输出该学生的全部成绩,用指针函数来实现
void test2(){
int score[][4]={{61,71,81,91},{62,72,82,92},{63,73,83,93},{64,74,84,94}};
int *p;
int i,j;
printf("输入序号");
scanf("%d", &i);
p = search(score, i);
for (j = 0; j <4; ++j) {//遍历这个学生的成绩
printf("%d\t", *(p + j));
}
}
int *search(int (*pointer)[4],int n){//先锁定某一个学生
int *pt;
pt = *(pointer + n);
return pt;
}
int main(){
test2();
return 0;
}
小知识
指针函数和函数指针的区别:
指针函数是带指针的函数,本身还是函数
函数指针是指向函数的指针变量,因而函数指针本身首先应该是指针变量,只不过该指针变量指向函数