Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

我记不住的那些C语言指针骚操作

背景: 最近在复习数据结构和算法,顺带刷刷题,虽然很长时间不刷题了但还是原来熟悉的味道,每一次重学都是加深了上一次的理解。本次我们看一下如何将C语言的指针。

C语言的指针是真精髓,用好了能写出非常优雅且简洁的代码,也是学习C语言迈不过去的一道坎,学会指针感觉就学会了C语言,变量、数组、字符串、函数 统统都需要指针。下面我们来记录一下这些指针的用法和技巧。

1. 变量指针

*a 是指 "a指向的变量",而不是"a指向的变量所拥有的值", 换句话说*a是变量,而不是值(这一点非常重要)

*a = *a + 1 ;      // 就是让a指向的变量自增1 ,*a同时可以在 赋值符号的左侧,又可以放在右侧
 


int* a ;  //声明的变量a是指向int型变量的指针,

a=&b ;  //是把变量b的地址存在指针a中

*a      ;  // 表达式*a 代表a指向的变量,即可以放置赋值符号的左侧,又可以放在右侧,

*a 是指 "a指向的变量",而不是"a指向的变量所拥有的值", 换句话说*a是变量,而不是值(这一点非常重要)

*a = *a + 1 ;      // 就是让a指向的变量自增1 ,*a同时可以在 赋值符号的左侧,又可以放在右侧。

等效于(*a)++ ;  //  代表 *a的值自增1. 

*a++;    //  这个不加括号,就不一样了, ++的优先级高于 * 所以 等效于 *(a++);  代表先返回*a的值,然后地址自增1

2. 数组指针

这一部分可参考之前写的文章:

我记不住的那些编程语言的语法(数组)-1_Penguinbupt的博客-CSDN博客

我记不住的那些C语言的二维数组的函数传参_Penguinbupt的博客-CSDN博客

例如: 二维数组 int arr[4][2];   那么arr是一个什么类型? 是一个 int arr[][2]的类型

例如: 二维数组 int arr[4][2];   如何将arr转换为一个一维数组,传参是什么?

例如: int** arr 与 int arr[4][2]; 这两者有什么关系和不同? 为什么?

例如: int arr[5] ;   那么 arr 是什么?  &arr是什么?

arr指向第一个元素,类型是 int*,它指向第一个元素,offset是 int为1

arr+1  等效于  &arr[1]

&arr 它的类型是  int (*)[5] ,它指向第一个元素,offset是这个数组的长度为5

&arr + 1 指向的是下一个 int(*)[5]

这两篇文章包括了一维、二维数组的知识,非常有用,这里不再赘述。

arr 和 &arr 类型不一样而已

arr[2] == *(arr + 2)

&arr[2] == &(*(arr + 2)) == arr + 2

char * argv[] 等效于 char ** argv

char *argv[] = declare argv as array of pointer to char

char **argv = declare argv as pointer to pointer to char

argv ---> argv[0] ---> "program"
          argv[1] ---> "arg1"
          argv[2] ---> "arg2"
           ...          ...
          argv[argc] == NULL


int arr[5] = {10,20,30,40,50};

如果arr指向地址为1000, arr+1则指向 1004, &arr+1则指向 1020,即 1000+5*4

arr is an integer pointer (int*) which points the first element of the array.
arr是一个指针,类型是 int* ,指向数组第一个元素,offset为一个元素的长度

&arr是一个指针,类型是 (int*)[5],指向整个数组,所谓的指向整个数组,其实就是offset为5个元素的长度即(int*)[5],
&arr + 1 

3. 字符串指针

字符串指针,其实就是 char* 类型的指针, 也会和数组一起使用,例如: 字符串数组

定义一个 字符串的数组,类似于 void main( int agrc, char* argv[]);

例如: 如何定义一个 字符串数组?      

两种方式:

第一种是: char arr[3][10] = {"Geek","Geeks", "Geekfor"};   // 3代表3个字符串,10代表每个字符串的最大字符数,但是这种 非常浪费空间,因为每个字符串预留了10个字符的地方,有的字符串只用5个或更少。

第二种是char* arr[] = {"Geek","Geeks","Geekfor"};  // 优点: 1. 可以任意修改各个字符串    2. 还不会浪费内存空间,其实就是为了节省空间,我们可以使用指针数组,是一个一维的数组,里面存储的是指针

4. 函数指针

函数指针非常有灵活,有 函数指针指向的是 函数代码的起始位置,和普通的指针不一样他们指向的是数据,而函数指针指向的是代码。

它的作用和要点:

(1)函数名可以获取函数地址, &函数名也能获取函数地址

(2)也可以定义一个数组,存放多个函数指针,并进行赋值

(3)同时 也可以作为 函数的参数传入,也可以作为 函数的返回值进行返回。

(4)可以使用函数指针避免代码冗余,例如:函数qsort()可用于排序(升序或降序)或以数组结构的任何顺序,不仅仅这些,通过函数指针和空指针,qsort可以排序任意数据类型。

让我们看一下:

1.
首先定义print函数, 然后用 &print代表取print()函数的地址。

void print(int a) {
  printf("%d\n", a);
}

// 写一个函数原型,与print保持一致,才能进行赋值操作
// print_ptr 是一个指针,指向 print()函数的起始地址
// 注意,(*print_ptr)一定要写在圆括号里面,否则函数参数(int)的优先级高于*,整个式子就会变void* print_ptr(int)

void (*print_ptr)(int) = &print;

// 有了这个 print_ptr 指针就可以调用函数了

(*print_ptr)(10);

等效于  print(10); 

2.
// 规定: 函数名本身就是指向函数代码的指针,通过函数名就能获取函数地址
// 也就是说  print就是指针
print == &print 

print == (*print)

即:
print(10);  等效于  (&print)(10);

void (*print_ptr)(int) = print;  等效于  void (*print_ptr)(int) = &print;


print_ptr == print

print_ptr == &print

3. 结论

print(10);

(*print)(10);

(&print)(10);

(*print_ptr)(10);

print_ptr(10);

// (&print_ptr)(10); 这个是无法运行的

也就是说 正常使用函数进行定义的函数,它的 函数名,加*或加&,都是等效的
而通过定义一个函数指针,通过指向到目标函数, 它的 函数名和加* 是等效的,  加&是无法运行的。

4. 

print == &print == *print 三者都是一样的

print_ptr == *print_ptr 两者是一样的

5.

#include <stdio.h>
void add(int a, int b)
{
    printf("Addition is %d\n", a+b);
}
void subtract(int a, int b)
{
    printf("Subtraction is %d\n", a-b);
}
void multiply(int a, int b)
{
    printf("Multiplication is %d\n", a*b);
}
  
int main()
{
    // fun_ptr_arr is an array of function pointers
    void (*fun_ptr_arr[])(int, int) = {add, subtract, multiply};
    unsigned int ch, a = 15, b = 10;
  
    printf("Enter Choice: 0 for add, 1 for subtract and 2 "
            "for multiply\n");
    scanf("%d", &ch);
  
    if (ch > 2) return 0;
  
    (*fun_ptr_arr[ch])(a, b);
  
    return 0;
}

// 函数作为参数进行传递

#include <stdio.h>
  
// Two simple functions
void fun1() { printf("Fun1\n"); }
void fun2() { printf("Fun2\n"); }
  
// A function that receives a simple function
// as parameter and calls the function
void wrapper(void (*fun)())
{
    fun();
}
  
int main()
{
    wrapper(fun1);
    wrapper(fun2);
    return 0;
}

// qsort

#include <stdio.h>
#include <stdlib.h>
  
// A sample comparator function that is used
// for sorting an integer array in ascending order.
// To sort any array for any other data type and/or
// criteria, all we need to do is write more compare
// functions.  And we can use the same qsort()
int compare (const void * a, const void * b)
{
  return ( *(int*)a - *(int*)b );
}
  
int main ()
{
  int arr[] = {10, 5, 15, 12, 90, 80};
  int n = sizeof(arr)/sizeof(arr[0]), i;
  
  qsort (arr, n, sizeof(int), compare);
  
  for (i=0; i<n; i++)
     printf ("%d ", arr[i]);
  return 0;
}

重要结论

假设有一个函数名为 print,那么 &print 和 *print 三者是相同的,都是等价的,毫无差异,而为了简洁易读,一般情况下,函数名前面都不加*&

而新定义的函数指针print_ptr,那么 print_ptr 和 *print_ptr是等价的,&print_ptr不是等价的。

5. 各类型的指针

如何判断这个指针是做什么的?  或者如何解读一个指针?

顺时针螺旋式阅读法,具体请参考下面两篇文章:

Clockwise/Spiral Rule

C Right-Left Rule (Rick Ord's CSE 30 - UC San Diego)

添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Penguinbupt

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

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

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

打赏作者

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

抵扣说明:

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

余额充值