零基础入门C语言——指针

简介

地址和指针:

  • 内存区的每一个字节有一个编号,这就是地址。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元.
  • 知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此,又把变量的地址称为该变量的指针
  • 在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;
}

小知识

指针函数和函数指针的区别:
指针函数是带指针的函数,本身还是函数
 
函数指针是指向函数的指针变量,因而函数指针本身首先应该是指针变量,只不过该指针变量指向函数
 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值