一、取地址运算
1、地址意义
变量的储存位置
2、获取地址
运算符:&
用法:& + 变量名(变量类型)
作用:取得相应变量(变量类型)的地址
3、输出地址
利用%p
如下代码,可获取并输出变量a的地址
#include <stido.h>
int main(void){
int a;
printf("%p", &a);
return 0;
}
为编写方便,文章后续仅保留主函数内内容。
4、一些特性
a.编译器架构不同,变量地址的大小(所占字节数)不同。所以不能用int等一般变量类型的变量,储存地址
b.相邻变量的地址相差:该编译器下的变量地址大小(所占占字节数) 如:32位架构下,变量地址的大小为四个字节。则32位架构下相邻变量的地址大小相差四个字节。
c.数组变量的地址与该数组第一个元素相同,且相邻元素的地址相差:该编译器下的变量地址大小(所占占字节数)
二、指针
1、前言
scanf()作为内置函数,可以实现输入变量的值的功能。
int a;
scanf("%d", &a);
注意到输入scanf()函数的是变量a的地址 &a,说明scanf()有两个特点:
第一,scanf()中存在能稳定接收变量a地址的某个变量;
第二,scanf()有通过变量的地址访问改变函数外变量的能力。
所以C语言中存在“某个能保存变量的地址的变量”,并能通过这“某个变量”访问改变变量的值。这就是指针。
2、指针变量(专门储存变量地址的变量)
①定义指针变量
/*1*/int i1;
/*2*/int* i2;
/*3*/int *i3;
/*4*/int *i41, i42;
/*5*/int *i51, *i52;
如上列举一些可能的定义方式:
1号为普通int类型变量i1;
2、3号是等效的,表示指针i2、i3;
4号表示定义了一个指针i41和一个普通int类型变量i42;
5号表示定义了两个指针i51、i52。
重点区分4、5号的不同,避免错误。
②函数中调用指针变量
void f(int *p){
printf("%p", p);//此时p是变量i的地址
printf("%d", *p);
/*此时*p是变量i的值,*可以调用指针变量p储存地址的变量
因而可以通过改变*p的值改变函数外变量i的值*/
}
int i=0;
void f(&i);
如上,在函数f中定义指针变量,输入变量i的地址,即可在函数f中访问变量i。
注意,只有指针变量p被输入变量的地址后,*p才有意义,可以赋值。
通过指针修改变量k的值的代码如下:
//定义指针p和变量k
int *p;
int k=0;
//将k的地址赋予p,此时*p指向变量k
p = &k;
//通过访问*p改变变量k的值
*P = 1;
不能直接:
int *p = 0;
/*此时p不表示任何变量的坐标,是随机值。*p不指向任何变量,无意义
③常量指针
①int* const q;表示指向某一变量后,不能再被修改。
②const int* q;表示不能通过*q去修改所指变量的值.
③非const的指针可以加上const,当作const指针传入函数,传入后函数内为const指针,函数外无影响。(避免函数对外面变量的修改)
int i;
//表示q只指向i,不能再指向其他变量,地址本身不能改变
int*const q = &i;
q++;//无效
//表示q指向i,不能对*q赋值改变i的值,但是可以对i直接赋值
const int *q = &i;//const只要在*的前面即可。如:int const*q;
*q = 10;//无效
i = 10;//有效
3、指针的部分使用情景
①交换两个变量的值
void change(int *pa, int *pb){
int t = *pa;
*pa = *pb;
*pb = t;
}
如上:change()函数可交换两变量的值。
②函数返回多个值
a.指针变量返回多个结果
void maxmin(int a[], int len, int *min, int *max){
*min = *max = a[0]
for(int i=0; i<len; i++){
if(a[i] < *min){
*min = a[i];
}
if(a[i] > *max){
*max = a[i];
}
}
如上:maxmin()函数可以输出:输入数组中的最大值和最小值。
b.函数返回运算状态,结果通过指针返回
int divide(int a, int b, int *result){
int ret = 1;
if(b == 0){
ret = 0;
}
else{
*result = a/b;
}
return ret;
}
如上:divide()函数通过返回值ret判断除法是否成功,通过*result返回除法结果。
4、指针与数组
①数组变量本身表达地址,所以如下:
int a[10];
int *p=a;//无需用&取地址
p = &a[1]//但是数组的单元是变量,需要用&取地址
②[]运算符可以对数组做,也可以对指针做:
int a[10];
int *p=a;
//此时,p[0] == a[0]
③*运算符可以对指针做,也可以对数组做:
int a[10];
int *p=a;
//此时,*p == a[0]; *a == a[0]
④数组变量是const的指针(常量指针),所以不能被赋值。(不能等于其他数组变量)
5、指针运算
①指针与常数:
指针p加上n,实际上是对p所指的地址加上指针类型的字节数乘以n。计算后得到的p是原p后的第n个“变量”(可能存在,可能不存在)的地址。
int* p;
int a[10];
p = &a[0];
p+=5;//此时*p --> a[5]
p+=4;//此时*p --> a[9]
应用:利用*p++对数组进行遍历:
int* p;
int a[] = {1,2,3,4,5,6,7,8,9,-1};
p=a;
/* *运算优先级小于++,所以利用数组a最后一项,以下
循环可以提供一种新的遍历方式*/
for(*p != -1){
printf("%d\n", *p++);
}
②指针与指针(仅同类型指针,抵制使用不同类型的指针进行运算):
指针p、q。(p - q)会得到p和q之间有几个“变量”(可能存在,可能不存在)的地址。
int* p, q;
int a[10];
p = &a[2];
q = &a[6];
int sum = p-q//此时sum = (6-2) = 4
③0地址(用NULL表示)
0地址的指针是无效指针,函数中可用0地址的指针表示返回的指针无效(?理论上,尚未经实践。。。)
int *p = NULL;//仅作演示,实际编程不应让指针为0值
6、malloc函数
需函数头:
#include <stdio.h>
#include <stdlib.h>
一般用于定义动态内存数组:
(用法及注意事项记录于注释)
int number;
int *a;
printf("输入所需变量数量:");
scanf("%d", &number);
a = (int*)malloc(number*sizeof(int));
/*首先,malloc(n)表示向系统请求一块大小为n个字节
的内存,所以n = 变量数乘以变量类型的字节数。然后,
malloc的返回值的类型是void*,所以需要(变量类型*)
转化为所需的变量类型。*/
//上述操作完成后,可当成数组a[number]使用
free(a);
/*程序结束时需将请求的内存返回,注意不要更改指针a的
值,返回时a必须为请求的内存的第一个变量地址(别改a或者记得改回去就行)