指针

取地址

内存分配的任务是通过指针去执行的,每一个变量在内存中都会有一个位置,可以通过& 运算符去访问变量在内存中的地址

地址的大小是否与int相同取决于编译器

运算符&

获得变量的地址,它的操作数必须是变量

scanf("%d", &x);

&只能对一个明确的变量去取地址,不能对没有地址的东西取地址(包括表达式和运算)

输出格式

%p 可以输出变量的地址

printf("%p", &x); 输出x变量在内存中的地址

相邻变量的地址

本地变量在内存存放在堆栈里,堆栈是自顶向下分配的,先定义的变量在后定义变量的上方,紧紧相连,但地址间隔着一个int大小的字节

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
int i = 1;
int j;
printf("%p\n", &i); // 0x7ffee5e29b58
printf("%p", &j); // 0x7ffee5e29b54
return 0;
}
数组的地址

整个数组的地址与数组中第一个元素即x[0]相同

相邻的数组元素的地址相隔一个int的字节

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
int x[5];

printf("%p\n", &x); // 0x7ffee5dd7b40
printf("%p\n", &x[0]); // 0x7ffee5dd7b40
printf("%p\n", &x[1]); // 0x7ffee5dd7b44
}

指针

保存地址的变量

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址 就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明

格式

数据类型 *变量名 = &变量名

int *p = &x;变量p的值存储着变量x在内存中的地址

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
int x = 2;
int *p = &x;

printf("%p\n", &x); // 0x7ffee2330b58
printf("%p\n", p); // 0x7ffee2330b58
return 0;
}

int *p,i;是定义了一个指针类型p和int类型i,并非两个都是指针,需要二者都是指针时需要在第二个变量前也加上*

指针变量

指针变量的值就是普通变量在内存的地址,而普通变量的值是实际意义上的值

指针作为参数

int i = 0;x(&i); 把变量i在内存中的地址通过传参的方式传递给函数x

void x(int *p)在被调用的时候得到了变量i的地址

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
void x(int *p);
int main() {
int i = 1;
printf("&i = %p\n", &i); // 0x7ffee3744b58
x(&i);
return 0;
}
void x(int *p) {
printf("p = %p\n", p); // 0x7ffee3744b58
}
通过指针访问变量

*是一个弹幕运算符,可以用来访问指针的值(变量的地址)所表示的变量的值

指针类型的变量可以作为左值也可以作为右值,与上一篇笔记中谈到的数组一样,当它在左边时是作为写入的方式,在右边时是被读取

左值与右值并非变量而是特殊的值,是表达式计算的结果

int i = *p 把指针p所指向的变量的值赋值给i

*p = i + 1; i + 1的值赋值给指针p所指向的变量的值

有点绕,但是不难理解~

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
void x(int *p);
int main() {
int i = 1;
printf("&i = %p\n", &i); // 0x7ffee3744b58
printf("i = %d\n", i); // 1
x(&i);
return 0;
}
void x(int *p) {
printf("p = %p\n", p); // 0x7ffee3744b58
printf("*p = %d\n", *p); // 1
}

⇩因为传递给x的是i在内存中的地址,所以当我们修改*p也就是修改变量i在内存中地址上实际的值即 *p= i = 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void x(int *p);
int main() {
int i = 1;
printf("&i = %p\n", &i); // 0x7ffee3744b58
printf("i = %d\n", i); // 1
x(&i);
printf("i = %d\n", i); // 2
return 0;
}
void x(int *p) {
printf("p = %p\n", p); // 0x7ffee3744b58
printf("*p = %d\n", *p); // 1
*p = 2; // 将2的值赋值给了实际变量i
}
&*互相反作用
  • *&

new = *&i = new = *(&i) = new = *(0x7ffee3744b58) = new = i

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main() {
int i = 1;
printf("&i = %p\n", &i); // 0x7ffee3744b58
printf("i = %d\n", i); // 1
int *p = &i;
printf("*p = %p\n", p); // 0x7ffee3744b58
int new = *&i;
printf("new = %d", new); // 1
return 0;
}
  • &*同理

注意事项

别在定义指针后还没指向任何变量就开始使用指针

一定要按照规范写法去定义指针变量int *p = &x;

数组与指针

  • 数组变量是特殊的指针,数组变量本身表达的就是在内存中的地址

    int a[10]; int *p =a; 无需使用&取地址

  • 但是数组中的元素表达的是单个的变量,这时就需要使用&去取地址

    a == &a[0] a的地址和a[0]的地址是一样的概念

  • 甚至[]运算符不仅可以对数组用,还可以对指针使用

    int x = 2;int *p = & x;p[0] == x[0]可以将普通变量x看作是只有一个元素的数组,所以p[0] = 2

    06-03_17:29

  • 除了可以读取值以外还能往数组里写入值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>

    int main() {
    int x = 2;
    int *p = &x;
    printf("x = %d\n", x); // x = 2
    printf("p[0] = %d\n", p[0]); // p[0] = 2
    p[0] = 4;
    printf("x = %d", x); // 被修改后x = 4

    return 0;
    }
函数_数组传值
  • 函数参数表中的数组实际上是指针,即sizeof[a] == sizeof (int *)

  • 在函数中声明的时候可以用*数组名称的方式,还可以继续用数组的运算符[]进行计算

数组参数的等价关系

在参数表和函数原型中它们的表达意思都是一样的

int sum(int *a,int n); == int sum(int *,int); == int sum(int a[],int n); == int sum(int [],int);

指针与const

const指针
  • *在const的左边表示指针是const

    当const指针指向了某个变量的地址之后,就无法再指向其他变量了,即无法修改变量的地址(指针的值)了

    p = &y无法通过编译,因为变量p是const类型的,不能被分配任何值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>

    int main() {
    int x = 2;
    int y = 3;
    int * const p = &x;
    printf("x = %d\n" ,*p); // 2
    *p = 30;
    printf("x = %d" ,*p); // 30
    p = &y; // Cannot assign to variable 'p' with const-qualified type 'int *const'
    return 0;
    }
  • *在const右边表示指针变量是const

    const指针变量指向了某个变量的地址之后,就不能通过这个指针去修改那个变量了,并非真正意义上让普通变量变成了const

    *p = 30无法通过编译,因为指针变量*p是一个只读的变量

    其他修改指针的操作可以正常编译

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <stdio.h>

    int main() {
    int x = 2;
    int y = 3;
    int const *p = &x;
    printf("x(%%p) = %p\n", p); // 0x7ffeebb2ab58
    printf("x(%%d) = %d\n", *p); // 2
    *p = 30; // Read-only variable is not assignable
    p = &y;
    printf("y(%%p) = %p", p); // 0x7ffeebb2ab54
    return 0;
    }
转换const

你可以将一个普通变量转换成const类型,让其在函数中无法被修改

const数组

因为数组已经是一个特殊的const类型的指针了,所以在数组前加上const能让数组中的每个元素都变成const类型的变量

const int x[] = {1,2,3,4,5,6,7,8,9,10,};

当用这种方式创建数组时,就需要在初始化时就对数组进行赋值,因为你无法再修改数组中的元素了

因为把数组传入给函数时传递的是地址,所以在函数中可以修改数组的值,所以传值给函数的时候也可以让函数声明数组是const,这样就能保护数组在函数中不会被修改

int a(const int x[]);

指针运算

指针是用十六进制的地址表示普通变量的值,如果对指针执行算数运算可以让地址指向的数组元素向前或向后移动

如果指针不是指向一片如数组一样连续分配的空间,那么这种运算没有意义可言

加减、递增递减

让指针加1的操作并非让地址真的+1了,加的是sizeof的值,也就是指针所指的普通变量的数据类型的大小

  • char类型的指针+1,它的地址加上的是char类型的大小
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
char a[] = {0,1,2,3,4,5,6,7,8,9,};
char *p = a;
printf("char = %d\n", sizeof(char)); // 1
printf("p = %p\n", p); // 0x7ffee5bbeabe +1⇩
printf("p+1 = %p\n", p + 1); // 0x7ffee76f5abf
return 0;
}
  • 同理,当类型是int的时候,地址+1的操作就是加上一个int类型的大小
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
int a1[] = {0,1,2,3,4,5,6,7,8,9,};
int *p = a1;
printf("int = %d\n", sizeof(int)); // 4
printf("p = %p\n", p); // 0x7ffee7ac6aa0 +1⇩
printf("p+1 = %p\n", p + 1); // 0x7ffee7ac6aa4
return 0;
}
  • *p + 1没有意义,*(p + 1)才是指向数组的下一个元素

  • 因为*(p + 1) = a[1],*(p + n) = a[n]

  • 那么既然加法可以看作是向后移动,减法就是向前移动了

  • 递增递减相应的也是向前向后挪一个元素的动作

当两个指针相减的时候,减出来的差并非是地址的差,而是地址的差/数据类型大小的差

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
int a1[] = {0,1,2,3,4,5,6,7,8,9,};
int *p = a1;
int *p1 = &a1[5];
printf("int = %d\n", sizeof(int)); // 4
printf("p = %p\n", p); // 0x7ffee7ac6aa0
printf("p1 = %p\n", p1); // 0x7ffee9f67ab4
printf("p1-p = %d", p1 - p); // 5
return 0;
}

0xb4 = 180 - 0xa0 = 160 = 0x14 = 20

20 / 4(sizeof(int))= 5;

*p++

++的优先级比*来的高,所以先算p++的结果

作用于数组类的连续空间操作

使得遍历数组元素时有更高效率的办法

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
int a[] = {0,1,2,3,4,5,6,7,8,9,};
int *p = a,t = *p;
int size = sizeof(a) / sizeof(a[0]);
while ( *p < size && *p >= t ) {
printf("p = %d\n", *p);
*p++;
}
return 0;
}
比较

<,<=,==,>,>=,!=都可以使用,用来比较普通变量在内存中的地址的大小

因为数组的元素的地址是线性递增的,所以a[5] > a[4]

0地址

指针的地址不应该被写入0地址

但可以用0地址表示特殊的事情

  • 返回的指针是无效的
  • 指针没有被真正初始化(先初始化为0)

NULL在事先定义在C语言中,它表示0地址

类型

为了避免错用指针,不同类型的指针是不能直接互相赋值的

无论指向什么类型,所有的指针大小都是不一样的

void

转换类型

void* 表示指向了未知类型的指针

int *p = &i; void *q = (void*)p; 并没有改变p所指i变量的类型,而是从q去访问i的时候不再将i当做是int类型

动态内存分配

malloc

格式:数据类型 *变量名 = (数据类型*)malloc(x*sizeof(数据类型))

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

在使用malloc函数之前需要在头文件中添加stdlib.h

malloc 需要的是参数不是数组里有多少个元素,而是有多少个字节,用数组总共的元素数量去乘以int类型的字节数就能得到malloc需要的参数了

malloc返回的结果是void*所以还需要用强制转换成int类型的值才能当作元素个数使用

由于数组是特殊的指针,所以这里定义的指针可以拿来当数组去使用

free

一个malloc对应一个free,程序中出现malloc就带上free

释放内存,只能释放malloc使用的原始地址,即使被修改过也不行

free过的地址不能再次free

指针应用场景

  1. 交换两个变量的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>
    void swap(int *a,int *b);
    int main() {
    int a , b;
    a = 5;
    b = 6;
    swap(&a,&b);
    printf("a = %d,b = %d\n", a, b);
    return 0;
    }
    void swap(int *a,int *b){
    int tmp = *a;
    *a = *b;
    *b = tmp;
    }
  2. 返回多个值

    找出数组中最小和最大的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <stdio.h>
    void minmax(int a[], int size, int *min, int *max);
    int main() {
    int a[] = {1,2,3,4,5,6,7,8,9,12,14,17,22,25,39,44,55,99};
    int min,max;
    int size = sizeof(a)/ sizeof(a[0]);
    minmax(a,size,&min,&max); // 传递a数组,a数组的元素数量,min和max变量的地址
    printf("min = %d,max = %d", min, max);
    return 0;
    }
    void minmax(int a[], int size, int *min, int *max){
    int i;
    *min = *max = a[0]; // 初始化min和max变量的值为a数组第一个值
    for ( i = 1; i < size; i++ ) { // 历遍数组
    if ( a[i] < *min ){
    *min = a[i];
    } else if ( a[i] > *max ){
    *max = a[i];
    }
    }
    }
  3. 函数返回异常值

    函数返回运算的状态,而结果通过指针去返回

    当任何数值都是有效的可能结果时,得分开返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <stdio.h>

    int main() {
    int a = 5, b = 2;
    int c;
    if (divide( a, b, &c )){ // 将a,b的值,c的地址传递给divide函数
    printf("%d/%d=%d", a, b, c);
    }
    return 0;
    }
    int divide( int a, int b, int *res ){
    int ret = 1;
    if ( b == 0){
    ret = -1; // 如果分母是0的话返回错误值-1
    } else{
    *res = a / b; // 否则的话返回值是a除以b的值
    }
    return ret;
    }

啊这…写得也太多了吧

评论