C语言初阶——7.指针

C语言初阶——7.指针

1. 指针运算

1.1 算术运算
  • 加减+-
    指针与整数相加:表示指针指向下个变量。
    指针与整数相减:表示指针指向上个变量。
    指针与指针相减:两个指针的元素间隔个数。
int arr[]={100,101,102,103,104,105};
int* p = arr;
int* q;
for(int i=0;i<5;++i){
    q = p+i;
    printf("%d\n",*q);
}
for(int i=0;i<5;++i){
    p = q-i;
    printf("%d\n",*p);
}
printf("q-p=%d\n",q-p);
  • 自增自减++--
    指针能够算术运算,必然能够自增自减。
    在这里插入图片描述
int arr[]={1,2,3,4,5};
int* p = arr;
for(int i=0;i<5;++i){
    printf("%d\n",*p++);
}
for(int i=0;i<5;++i){
    printf("%d\n",*p--);
}

*p++/*p--

操作说明

  1. 操作数是指针
  2. 自增自减++--优先级高于解引用*

计算过程

  1. 运算++/--,返回的是p的值(地址),然后p自加/自减。
  2. 运算*,获取p指向的值。

等价于

*p;
p=p+1;
自增自减 相当于
*q++ *(q++)
*q-- *(q--)

*++p/*--p
操作说明

  1. 操作数是指针
  2. 前缀自增自减++--和解引用*的结合律是自右向左。
  3. 前缀自增自减++--在解引用*的右边,优先计算。

计算过程

  1. 运算++/--p自加/自减,返回的是p自加/自减后的值(地址)。
  2. 运算*,获取p指向的值。
自增自减 相当于
*++q *(++q)
*--q *(--q)

++*q/--*q

操作说明

  1. *操作数是指针,前缀自增自减++--操作数是指针指向的值。
  2. 前缀自增自减++--和解引用*的结合律是自右向左。
  3. 解引用*在前缀自增自减++--的右边,优先计算。

计算过程

运算*,获取p指向的值。
运算++/--p指向的值自加/自减。

自增自减 相当于
++*q ++(*q)
--*q --(*q)

如果一个表达式里有多个运算符,则先进行优先级比较,先执行优先级高的运算符;如果优先级相同,那就看结合性,根据结合方向来做运算。

扫描二维码关注公众号,回复: 10664525 查看本文章
  • 问题
    指针与指针可以相加吗?
    在不同数组中可以执行上面的操作吗?
    试一下编译下面程序
#include <stdio.h>

int main () {

    int arr[]={100,101,102,103,104,105};
    for(i=0;i<5;++i){
        printf("%p\n",arr++);
    }
    return 0;
}
  • 分析
    分析下面三段代码
    代码一
#include <stdio.h>

int main () {
    int arr[] = {1,2,3,4,5};
    int* p = arr;
    printf("*p++ = %d\n",*p++);
    printf("p index = %d\n",p - arr);
    for(int i=0;i<5;++i){
        printf("%d ",arr[i]);
    }
    printf("\n");
    return 0;
}

代码二

#include <stdio.h>

int main () {
    int arr[] = {1,2,3,4,5};
    int* p = arr;
    printf("*++p = %d\n",*++p);
    printf("p index = %d\n",p - arr);
    for(int i=0;i<5;++i){
        printf("%d ",arr[i]);
    }
    printf("\n");
    return 0;
}

代码三

#include <stdio.h>

int main () {
    int arr[] = {1,2,3,4,5};
    int* p = arr;
    printf("++*p = %d\n",++*p);
    printf("p index = %d\n",p - arr);
    for(int i=0;i<5;++i){
        printf("%d ",arr[i]);
    }
    printf("\n");
    return 0;
}

在这里插入图片描述

++/--一定会改变变量的值,前两种改变指针的值,后一种改变元素的值。

1.2 比较运算符

==!=<<=>>=
本质是比较内存中的地址。

#include <stdio.h>
int main () {
    int arr[]={100,101,102,103,104,105};
    int* p = arr;
    for(int i=0;i<5;++i){
        printf("%p\n",p++);
    }
    return 0;
}

数组中的元素地址线性递增。

1.3 单位长度

从上面可以看到,指针的加1减1,地址并非加1减1。

int iarr[] = {1,2,3,4,5,6};
int* p = iarr;
for(int i=0;i<5;++i){
    printf("%p\n",p++);
}

char carr[] = {1,2,3,4,5,6};
char* q=carr;
for(int i=0;i<5;++i){
    printf("%p\n",p++);
}
  • 应用范围
    指针的算术运算表示在一片连续空间上的移动。
    指针的比较运算也是用于一片连续空间的地址比较。
    常用于数组等连续内存。

2. 指针类型

  1. 无论指向什么类型,所有指针的大小都是一样的,都是地址的大小。
char* str;
short* ps;
int* pn;
long* pl;
long long* pll;
float* pf;
double* pd;
long double* pld;
  1. 指针类型转换

指向不同类型的指针不能直接相互赋值(特例void*),需要强制类型转换。

char* str = "abcd";
int* p = str;

指针类型转换没有改变指针内的地址,也没有改变指针指向的值,只是改变了移动的单位长度。

#include <stdio.h>
int main(){
    char* str = "abcdef";
    int* p=(int*)str;
    p++;
    char* q = (char*)p;
    printf("%c\n",*q);
}
  1. void类型的指针

void*是一种很特别的指针,表示指向未知类型的指针,并不指定它是指向哪一种类型的数据,而是根据需要转换为所需数据类型。

int n = 0;
int* p = &n;
void* q = p;
int* k = (int*) q;

指针作用小结

  1. 较大数据结构体传入时做参数。
  2. 传入数组后,对数组做操作。
  3. 函数需要多个返回值时,作为返回值参数。
  4. 动态申请内存。
  5. 避免使用未初始化指针、空指针和野指针。

3. 数组指针 vs 指针数组

3.1 数组指针

指向一个数组指针称为数组指针。

int n = 0;
int* p = &n;
int arr[] = {1,2,3,4,5,6};
int* q = arr;
3.2 指针数组

指针是一个类型,也可以组成一个数组,这样的数组称为指针数组。

#include <stdio.h>

int main(){
    int a = 1;
    int b = 2;
    int c = 3;
    int* p[] = {&a,&b,&c};
    for(int i=0;i<3;++i){
        printf("%d\n",*p[i]);
    }
    for(int i=0;i<3;++i){
        printf("%d\n",**(p+i));
    }
}

[]的优先级高于*,那么p先和[]结合,说明这是一个数组。再和int*结合,说明这个数组里的每个元素都是一个指针,每个元素都能保存一个地址。

4. 常量指针 vs 指针常量

4.1 常量指针const int *p

可以写作int const *ppint*类型,const修饰的是*p,所以*p是常量,表示p指向的地址里的值不可修改,也就是说,*p里的值不能再重新赋值了,但是可以修改p指向的地址。

int a = 10;
int b = 20;
const int *p = &a;
p = &b;      // 可以
*p = 100;    // 错误
4.2 指针常量int * const p

pint*类型,那么const修饰的是p,所以p是常量,表示p指向的地址不可修改,即p不能再指向别的地方了,但是可以修改p指向的这个地址里的值。

int a = 10;
int b = 20;
int * const p = &a;
p = &b;      // 错误
*p = 100;    // 允许
4.3 常量指针常量const int * const p

pint*类型,两个const分别修饰了p*p, 所以p*p都是常量,表示p指向的地址不可修改,同时p指向的地址里的值也不可修改

int a = 10;
int b = 20;
const int *const p = &a;
p = &b;       // 错误
*p = 100;     // 错误

在这里插入图片描述

自由的代价,是永远的警惕。-- C Primer Plus
你定义了一个指针,那就一定要知道这个指针指向的什么地方,而且你要保证这个指针是真实有效的,否则我就用程序崩溃来惩罚你。

No. 例子 名称 指向的值 地址
1 const int *p/int const *p 常量指针 不可改变 可改变
2 int* const p 指针常量 可改变 不可改变
3 const int * const p 常量指针常量 不可改变 不可改变

*之前的const修饰指向的变量,*之后的const修饰指针。

问题
下面的q是什么类型指针?

const int *p,*q;
int const *p,*q;
int* const p,*q;
const int * const p,*q;

0地址

#include <stdio.h>

int main(){
    int *p = 0;
    printf("%d\n",*p);
}

0地址是内存中不能访问的地址。在C语言中,标准库定义NULL表示0地址。
通常用来表示如下:

  1. 指针没有初始化
  2. 返回指针无效

补充

1. 字符串数组与字符串指针数组
1 字符串数组

字符串数组,可以看成二维字符数组,只是初始化可以使用字符串方式。

char arr[12][10] = {"January","February","March","April",
                   "May","June","July","August",
                   "September","October","November","December"};

简化

char arr[][10] = {"January","February","March","April",
                 "May","June","July","August",
                 "September","October","November","December"};

2 字符串指针数组

char* arr[12] = {"January","February","March","April",
                 "May","June","July","August",
                 "September","October","November","December"};

简化

char* arr[] = {"January","February","March","April",
               "May","June","July","August",
               "September","October","November","December"};

3 字符串数组与字符串指针数组的区别

  1. 大小的区别
char arr1[][10] = {"January","February","March","April",
                 "May","June","July","August",
                 "September","October","November","December"};
char* arr2[] = {"January","February","March","April",
               "May","June","July","August",
               "September","October","November","December"};

printf("sizeof(arr1)=%d\n",sizeof(arr1));
printf("sizeof(arr2)=%d\n",sizeof(arr2));
  1. 二维指针的区别
#include <stdio.h>

int main () {
    char arr1[][10] = {"January","February","March","April",
                 "May","June","July","August",
                 "September","October","November","December"};
    char* arr2[] = {"January","February","March","April",
               "May","June","July","August",
               "September","October","November","December"};
    char** p1 = arr1;
    for(int i=0;i<12;++i){
        printf("%s\n",p1[i]);
    }
    char** p2 = arr2;
    for(int i=0;i<12;++i){
        printf("%s\n",p2[i]);
    }
    return 0;
}
2. main()参数与返回值

1 main()参数

main()的完全形式是这样的。

int main(int argc,char* argv[])

或者

int main(int argc,char** argv)

其中,argc是命令与参数数量。argv是命令和参数组成的字符串数组。argv[0]是命令本身。其余是参数。

int main(int argc,char* argv[]){
    for(int i=0;i<argc;++i){
        printf("%d:%s",i,argv[i]);
    }
    return 0;
}

2 main()返回值

main()返回值是与调用程序交互的,返回程序执行状态。通常0表示执行成功,非零表示执行失败。

int main(){
    int n;
    printf("请输入一个整数:");
    scanf("%d",&n);
    return n;
}

在终端执行程序后,接着执行echo $?可以看到返回值。

发布了68 篇原创文章 · 获赞 16 · 访问量 8456

猜你喜欢

转载自blog.csdn.net/weixin_41969690/article/details/103733954
今日推荐