C语言初阶⑦(指针初阶)知识点+笔试题

目录

指针介绍

解引用操作

指针的大小

指针类型的意义

指针的类型决定了指针向前或者向后走一步有多大(距离)

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

野指针

指针运算

指针+-整数

指针-指针

指针的关系运算(比较大小)

指针和数组的多种写法:

二级指针

指针数组

笔试题

1. 下面代码的结果是:( )

2. 下面代码的结果是:( )

3. 字节大小

4. 下面代码的结果是:

5. 计算求和

6. 使用指针打印数组内容


指针介绍

什么是指针?指针就是地址,地址就是指针。

指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

变量都有地址,取出变量的地址如下:


#include <stdio.h>
int main()
{
    int a = 10; // a在内存中要分配空间 - 4个字节
    &a; // 取出num的地址
    printf("%p\n", &a); //%p - 以地址的形式打印

    return 0;
}

定义指针变量,存储地址

* 说明 pa 是指针变量

int 说明 pa 执行的对象是 int 类型的


int main()
{
    int a = 10;
    int* pa = &a; // pa是用来存放地址的,在C语言中叫pa的是指针变量
    
    char ch = 'w';
    char* pc = &ch;
    
    return 0;
}

解引用操作

如果把定义指针理解为包装成快递,那么“解引用操作”就可以理解为是拆包裹

拆出来的值就是那个变量的值,甚至还可以通过“解引用”来修改它的值。

解引用操作用法:


#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;

    *pa = 20; // * 解引用操作   *pa就是通过pa里边的地址,找到a
    printf("%d\n", a); //20

    return 0;
}

指针的大小

“指针的大小是相同的”

为什么相同?

“因为指针是用来存放地址的,指针需要多大空间,取决于地址的存储需要多大空间”

指针是一种复合数据类型,指针变量内容是一个地址,因此一个指针可以表示该系统的整个地址集合,

故按照32位编译代码,指针占4个字节,按照64位编译代码,指针占8个字节

(注意:不是64位系统一定占8个字节,关键是要按照64位方式编译)。

32位 - 32bit - 4byte

64位 - 64bit - 8byte

sizeof 计算指针的大小:


#include <stdio.h>
int main()
{
    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(long*));
    printf("%d\n", sizeof(long long*));
    printf("%d\n", sizeof(float*));
    printf("%d\n", sizeof(double*));
    //32位下都是 4         64位下都是 8
    return 0;
}

指针类型的意义

指针的类型决定了指针向前或者向后走一步有多大(距离)


#include <stdio.h>
//演示实例
int main()
{
    int n = 10;
    char* pc = (char*)&n;
    int* pi = &n;

    printf("%p\n", &n);
    printf("%p\n", pc);
    printf("%p\n", pc + 1);   //+1个字节
    printf("%p\n", pi);
    printf("%p\n", pi + 1);   //+4个字节
    return  0;
}

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。


int main()
{
    int a = 0x11223344;
    int* pa = &a; // 44 33 22 11 (至于为什么是倒着的,后面会讲。)
    *pa = 0;// 00 00 00 00
    
    char* pc = &a; // 44 33 22 11
    *pc = 0; // 00 33 22 11
    // 在内存中仅仅改变了一个字节
 
    // 解引用操作时就不一样了
    // 整型指针操作了4个字节,让四个字节变为0
    // 字符指针能把地址交到内存中,
    // 但是解引用操作时,只敢动1个字节
    return 0;
}

野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针的成因

① 指针未初始化;

② 指针越界访问;

③ 指针指向的空间已释放

如何规避野指针

1. 指针初始化

2. 小心指针越界

3. 指针指向空间释放及时置NULL

4. 避免返回局部变量的地址

5. 指针使用之前检查有效性

指针运算

指针+-整数

指针加整数:打印 1 2 3 4 5 6 7 8 9 10


#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr; // 指向数组的首元素 - 1
 
    for(i=0; i<sz; i++) 
    {
        printf("%d ", *p);
        p = p + 1; //p++   第一次循环+1之后指向2
    }
 
    return 0;
}

指针减整数:打印 10 8 6 4 2


#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = &arr[9]; // 取出数组最后一个元素的地址
    
    for(i=0; i<sz/2; i++)
    {
        printf("%d ", *p);
        p = p - 2;
    }
    return 0;
}

指针-指针

(指针+指针无意义)

指针减指针得到的是元素之间元素的个数;

注意事项:当指针减指针时,他们必须指向同一空间(比如同一个数组的空间);

指针减指针:


int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%d\n", &arr[9] - &arr[0]); // 得到指针和指针之间元素的个数
 
    return 0;
}

int my_strlen(char* s)
{
    char* p = s;
    while (*p != '\0')
    {
        p++;
    }
    return p - s;
}

指针的关系运算(比较大小)

指针减减指针:


#define N_VALUES 5
 
int main()
{
    float values[N_VALUES];
    float *vp;
 
    for(vp=&values[N_VALUES]; vp> &values[0]; )
    {
        *--vp = 0; //前置--
    }
    
    return 0;
}

简化(这么写更容易理解,上面代码 *--vp在最大索引后的位置开始访问的):


int main()
{
    float values[5];
    float *vp;
 
    for(vp=&values[N_VALUES]; vp> &values[0]; vp--) 
    {
        *vp = 0;
    }
    
    return 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的

那个内存位置的指针进行比较。

指针和数组的多种写法:

数组名表示的是数组首元素的地址

sizeof(数组名):计算的是整个数组的大小,单位是字节


int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int sz = sizeof(arr) / sizeof(arr[0]);// 40 / 4 = 10
    printf("%d\n", sz);//10
    
    return 0;
}

二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?

这就是 二级指针 。

概念:指针变量也是变量,是变量就有地址,指针的地址存放在二级指针;

依次套娃还有三级指针,四级指针,但我们一般最多用到二级指针.....


#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;
    int** ppa = &pa; // ppa就是二级指针
    int*** pppa = &ppa; // pppa就是三级指针
    ...
    
    **ppa = 20;
    printf("%d\n", *ppa); // 20
    printf("%d\n", a);  // 20
 
    return 0;
}

对于二级指针的运算:

① *ppa 通过对 ppa 中的地址进行解引用,找到了的是 pa,*ppa 其实访问的就是pa;

② **ppa 先通过 *ppa 找到 pa,然后对 pa 进行解引用操作,*pa 找到的就是 a;

指针数组

概念:指针数组本质上是数组,存放指针的数组;

注意:不要和数组指针混淆,数组指针本质上是指针;

指针数组是指针还是数组?

答案:是数组。是存放指针的数组。

数组我们已经知道整形数组,字符数组。

分析下面的数组:


int arr1[5];
char arr2[6];
int* arr3[5];

解析:

① arr1 是一个整型数组,有 5 个元素,每个元素都是一个 整型;

② arr2 是一个字符数组,有 6 个元素,每个元素都是一个 char 型;

③ arr3 是一个整型指针数组,有 5 个元素,每个元素是一个 整型指针;

笔试题

1. 下面代码的结果是:( )


#include<stdio.h>
int main()
{
    int arr[] = { 1,2,3,4,5 };
    short* p = (short*)arr;
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        *(p + i) = 0;
    }

    for (i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

A.1 2 3 4 5

B.0 0 3 4 5

C.0 0 0 0 5

D.1 0 0 0 0

答案解析:

arr数组在内存中的存储格式为:

0x00ECFBF4: 01000000

0x00ECFBF8: 02000000

0x00ECFBFC: 03000000

0x00ECFC00: 04000000

0x00ECFC04: 05000000

指针p的类型为short*类型的,因此p每次只能所有两个字节,for循环对数组中内容进行修改时,一次访问的是:

arr[0]的低两个字节,arr[0]的高两个字节,arr[1]的低两个字节,arr[1]的高两个字节,故改变之后,数组中内容如下:

0x00ECFBF4: 00000000

0x00ECFBF8: 00000000

0x00ECFBFC: 03000000

0x00ECFC00: 04000000

0x00ECFC04: 05000000

故最后打印:0 0 3 4 5

因此:选择B


2. 下面代码的结果是:( )


#include<stdio.h>
int main()
{
    int a = 0x11223344;
    char* pc = (char*)&a;
    *pc = 0;
    printf("%x\n", a);
    return 0;
}

A.00223344

B.0

C.11223300

D.112233

答案解析:

假设,a变量的地址为0x64,则a变量在内存中的模型为:

0x64| 44 |

0x65| 33 |

0x66| 22 |

0x67| 11 |

char*类型的指针变量pc指向只能指向字符类型的空间,如果是非char类型的空间,必须要将该空间的地址强转为char*类型。

char *pc = (char*)&a; pc实际指向的是整形变量a的空间,即pc的内容为0x64,即44,

*pc=0,即将44位置中内容改为0,修改完成之后,a中内容为:0x11223300

因此:选择C


3. 字节大小

以下系统中,int类型占几个字节,指针占几个字节,操作系统可以使用的最大内存空间是多大:( )

A.32位下:4,4,2^32 64位下:8,8,2^64

B.32位下:4,4,不限制 64位下:4,8,不限制

C.32位下:4,4,2^32 64位下:4,8,2^64

D.32位下:4,4,2^32 64位下:4,4,2^64

答案解析:

32位系统下:

int占4个字节,指针表示地址空间个数,总共有2^32个,故占4个字节

64位系统下:

int占4个字节,指针表示地址空间个数,总共有2^64个,故占8个字节

因此:选择C


4. 下面代码的结果是:


#include<stdio.h>
int i;
int main() 
{
    i--;
    if (i > sizeof(i))
    {
        printf(">\n");
    }
    else
    {
        printf("<\n");
    }
    return 0;
}

A. >

B. <

C.不输出

D.程序有问题

答案解析:

C语言中,0为假,非0即为真。

全局变量,没有给初始值时,编译其会默认将其初始化为0。

i的初始值为0,i--结果-1,i为整形,sizeof(i)求i类型大小是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形,因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形是一个非常大的数字,超过4或者8。

这道题很隐蔽

因此:选择A


5. 计算求和

【题目内容】

求Sn=a+aa+aaa+aaaa+aaaaa+......的前n项之和,其中a是一个数字,

例如:2+22+222+2222+22222

输入:a和n


//通过观察可以发现,该表达式的第i项中有i个a数字,因此:
//假设第i项为temp,则第i+1项为temp*10+a
#include<stdio.h>
int main()
{
    int a = 0, n = 0, temp = 0, sum = 0;
    scanf("%d%d", &a, &n);
    for (int i = 1;i <= n;i++)
    {
        temp = temp * 10 + a;
        sum += temp;
    }
    printf("%d\n", sum);
    return 0;
}

6. 使用指针打印数组内容

【题目内容】

写一个函数打印arr数组的内容,不使用数组下标,使用指针。

arr是一个整形一维数组。


#include<stdio.h>
void print_arr(int *p,int sz)
{
    for (int i = 0;i < sz;i++)
    {
        printf("%d ", *(p + i));// *p: 取到p所指向位置的元素
    }
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    print_arr(arr, sz); // 数组名代表数组首元素的地址
    return 0;
}

本篇完。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GR鲸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值