C指针理解,题目

1、指针

指针本质上就是一种保存变量地址的变量

2、指针优势

使用指针可以使代码更紧凑、更高效
1、可以让不同区域的函数共享内存数据,代码更高效
2、定义复杂的数据结构,Eg:链表,二叉树
3、通过被调函数修改调用函数的对象

3、声明指针

int *p 声明了一个int类型的指针
int *p[10], 声明了一个指针数组,有10个指针,每个元素指向一个int类型的指针
int (*p)[10] 声明了一个数组指针,该指针指向一个int类型的一维数组
int **p 声明了一个指针p,该指针指向的是一个int类型的指针

int x=1int *p=&x;

int *p;
p=(int*)malloc(sizeof(int));
free(p)

4、指针运算

C 指针的算术运算只限于两种形式:

1) 指针 +/- 整数 :
可以对指针变量 p 进行 p++、p–、p + i 等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i 个操作数。
在这里插入图片描述

注意:加减的是地址
2)指针 - 指针
只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。举个例子:

 #include "stdio.h"

int main(){
    int a[10] = {1,2,3,4,5,6,7,8,9,0};
    int sub;
    int *p1 = &a[2];
    int *p2 = &a[8];

    sub = p2-p1;                                                                            
    printf("%d\n",sub);    // 输出结果为 6

    return 0;
}

5、数组与指针

int a[10]
int *p
p=a 或p=&a[0];

数组和指针并不是完全等价,指针是一个变量,而数组名不是变量数组可以看做是一个用于保存变量的容器,数组的首个元素的地址与数组的地址是一致的
Eg:

#include "stdio.h"                                                                          
int main(){
    int x[10] = {1,2,3,4,5,6,7,8,9,0};
    int *p = x;
    printf("x的地址为:%p\n",x);
    printf("x[0]的地址为:%p\n",&x[0]);
    printf("p的地址为:%p\n",&p);      // 打印指针 p 的地址,并不是指针所指向的地方的地址

    p += 2;
    printf("*(p+2)的值为:%d\n",*p);    // 输出结果为 3,*(p+2)指向了 x[2]

    return 0;
}

指针数组
int *p[10] 可以用来在操作大量数据时候使用

数组指针
数组指针是一个指针,它指向一个数组

int (*p)[10]

由于 () 的优先级最高,所以 p 是一个指针,指向一个 int 类型的一维数组,这个一维数组的长度是 10,这也是指针 p 的步长。也就是说,执行 p+1 时,p 要跨过 10 个 int 型数据的长度
数组指针与二维数组联系密切,可以用数组指针来指向一个二维数组,如下:
数组相关补充
int arr[3] [4];`//这表示定义了一个3行4列的二维数组,并且行优先
有以下几种表示二维数组元素的方法:

int brr [3] [4] ={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

int crr [3] [4] = {1,2,3,4,5,6,7,8,9,10,11,12};

Int err [3][4] = {1,2,3,4,5};

当表示某一特定元素时,例如2行1列的元素1时,可用以下两种方式来表示

frr [2][1]=1;

int hrr[3][4]={0,0,0,0,0,0,0,0,0,1};

int grr[][4]={1,2,3,4,5,6,7,8,9,10};//二维数组中行可以省略,至少写出列,编译器可以推断出来

一维数组数组名表示数组首元素

int arr[5];

arr:int*

arr+1:  int*

arr[0]: 

二维数组,

int brr[3][4] 

数据类型

1.brr:          int(*p)[4] //首行的地址

2.brr+1:        int(*p)[4]//下一行的地址

3.brr[0]:        int* //brr中第0列元素的地址,(brr[0]==arr)

4.brr[0]+1:      int*//brr[0][1]

5.brr[0][0]:      int

#include "stdio.h"
 
 int main(){
     int arr[2][3] = {1,2,3,4,5,6};               // 定义一个二维数组并初始化 2行3列
     int (*p)[3];                                 // 定义一个数组指针,指针指向一个含有3个元素的一维数组
 
     p = arr;                                     // 将二维数组的首地址赋给 p,此时 p 指向 arr[0] 或 &arr[0][0]
     printf("%d\n",(*p)[0]);              // 输出结果为 1
     p++;                         // 对 p 进行算术运算,此时 p 将指向二维数组的下一行的首地址,即 &arr[1][0]
     printf("%d\n",(*p)[1]);                      // 输出结果为5
 
     return 0;                                                                               
 }

注意:优先级 () >[ ]> *

6、指针与结构

C语言中使用 -> 操作符来访问结构指针的成员

#include "stdio.h"

typedef struct{
    char name[10];
    int age;
    int score;  
}message;

int main(){
    message mess = {"tongye",23,83};
    message *p = &mess;

    printf("%s\n",p->name);      // 输出结果为:tongye
    printf("%d\n",p->score);         // 输出结果为:83

    return 0;
}

7、指针与函数

C语言的所有参数均是以“传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。
  指针作为函数的参数
   传值调用的好处是是被调函数不会改变调用函数传过来的值,可以放心修改。但是有时候需要被调函数回传一个值给调用函数,这样的话,传值调用就无法做到。为了解决这个问题,可以使用传指针调用。指针参数使得被调函数能够访问和修改主调函数中对象的值。用一个例子来说明:

   #include "stdio.h"

void swap1(int a,int b)        // 参数为普通的 int 变量
{
  int temp;
  temp = a;
  a = b;
  b = temp;
}

void swap2(int *a,int *b)      // 参数为指针,接受调用函数传递过来的变量地址作为参数,对所指地址处的内容进行操作
{
  int temp;                    // 最终结果是,地址本身并没有改变,但是这一地址所对应的内存段中的内容发生了变化,即x,y的值发生了变化
  temp = *a;
  *a = *b;
  *b = temp;
}

int main()
{
  int x = 1,y = 2;
  swap1(x,y);                     // 将 x,y 的值本身作为参数传递给了被调函数
  printf("%d %5d\n",x,y);         // 输出结果为:1     2

  swap(&x,&y);                    // 将 x,y 的地址作为参数传递给了被调函数,传递过去的也是一个值,与传值调用不冲突
  printf("%d %5d\n",x,y);         // 输出结果为:2     1
  return 0;
}

指向函数的指针
在C语言中,函数本身不是变量,但是可以定义指向函数的指针,也称作函数指针,函数指针指向函数的入口地址。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。 声明一个函数指针的方法如下:

返回值类型 (* 指针变量名)([形参列表]);

int (*pointer)(int *,int *); // 声明一个函数指针

上述代码声明了一个函数指针 pointer ,该指针指向一个函数,函数具有两个 int * 类型的参数,且返回值类型为 int。下面的代码演示了函数指针的用法:

#include "stdio.h"
#include "string.h"

int str_comp(const char *m,const char *n);                             // 声明一个函数 str_comp,该函数有两个 const char 类型的指针,函数的返回值为 int 类型
void comp(char *a,char *b,int (*prr)(const char *,const char*));       // 声明一个函数 comp ,注意该函数的第三个参数,是一个函数指针

int main()
{
    char str1[20];      // 声明一个字符数组
    char str2[20];
    int (*p)(const char *,const char *) = str_comp;            // 声明并初始化一个函数指针,该指针所指向的函数有两个 const char 类型的指针,且返回值为 int 类型
    gets(str1);         // 使用 gets() 函数从 I/O 读取一行字符串
    gets(str2);
    comp(str1,str2,p);  // 函数指针 p 作为参数传给 comp 函数

    return 0;
}

int str_comp(const char *m,const char *n)
{
   // 库函数 strcmp 用于比较两个字符串,其原型是: int strcmp(const char *s1,const char *s2);
    if(strcmp(m,n) == 0) 
        return 0;
    else
        return 1;
}

/* 函数 comp 接受一个函数指针作为它的第三个参数 */
void comp(char *a,char *b,int (*prr)(const char *,const char*))
{
    if((*prr)(a,b) == 0)
        printf("str1 = str2\n");
    else
        printf("str1 != str2\n");
}

8、相关题目

1、数组指针

int a[5] = { 1, 2, 3, 4, 5 };  
int *ptr = (int *)(&a + 1);  
printf("%d,%d", *(a + 1), *(ptr - 1)); 

分析:

答案是2,5。本题的关键是理解指针运算,”+1“就是偏移量的问题:一个类型为T的指针移动,是以sizeof(T)为单位移动的。

a+1:在数组首元素地址的基础上,偏移一个sizeof(a[0])单位。因此a+1就代表数组第1个元素,为2;

&a+1:在数组首元素的基础上,偏移一个sizeof(a)单位,&a其实就是一个数组指针,类型为int()[5]。因此&a+1实际上是偏移了5个元素的长度,也就是a+5;再看ptr是int类型,因此"ptr-1"就是减去sizeof(int*),即为a[4]=5;

a是数组首地址,也就是a[0]的地址,a+1是数组下一个元素的地址,即a[1]; &a是对象的首地址,&a+1是下一个对象的地址,即a[5]。

2、二级指针
给定声明 const char * const *pp;下列操作或说明正确的是?

(A)pp++  (B)(*pp)++  (C)(**pp)=\c\;  (D)以上都不对

分析:

答案是A。

先从一级指针说起吧:
(1)const char p : 限定变量p为只读。这样如p=2这样的赋值操作就是错误的。
(2)const char p : p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如p=4这样的操作就错了, 因为企图改写这个已经被限定为只读属性的对象。

(3)char const p : 限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而p=3这样的操作合法,因为并没有限定其最终对象为只读。
(4)const char const p :两者皆限定为只读,不能改写。
再来看二级指针问题:
(1)const char p : p为一个指向指针的指针,const限定其最终对象为只读,显然这最终对象也是为char类型的变量。故像p=3这样的赋值是错误的, 而像
p=? p++这样的操作合法。

(2)const char * const *p :限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的,但是p++这种是合法的。
(3)const char * const * const p :全部限定为只读,都不可以改写

此题目可理解为p先是指针,再是*p取值 const p 就是指针不能变,const *p就是指针指向的值不能变

3、函数参数为指针应小心

下面的代码有什么问题?运行结果会怎么样?

void GetMem(char *p)
{
    p = (char*)malloc(100);   
}
void main()
{
    char *str = NULL;
    GetMem(str);
    strcpy(str, "hello word!");

    printf(str);
}

分析:

程序崩溃。在上面已经分析过了,传递给GetMem函数形参的只是一个副本,修改形参p的地址对实参str丝毫没有影响。所以str还是那个str,仍为NULL,这时将字符串常量拷贝到一个空地址,必然引发程序崩溃。下面的方法可以解决这个问题:

void GetMem(char **p)
{
    *p = (char*)malloc(100);   
}
void main()
{
    char *str = NULL;
    GetMem(&str);
    strcpy(str, "hello word!");
    printf(str);
   free(str);   //不free会引起内存泄漏
}

看似有点晦涩,其实很好理解。本质上是让指针变量str指向新malloc内存的首地址,也就是把该首地址赋值给指针变量str。前面我们说过,指针传递本质上也是值传递,要想在子函数修改str的值,必须要传递指向str的指针,因此子函数要传递的是str的地址,这样通过指针方式修改str的值,将malloc的内存首地址赋值给str。

参考文档:

https://www.cnblogs.com/tongye/p/9650573.html
https://blog.csdn.net/xiaobaibai915/article/details/79943659
https://blog.csdn.net/qq_42304888/article/details/97369955

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值