C语言学习笔记—指针
未完待续
指针
- 内存单元是存储数据的具体实体;
- 地址是用于定位内存单元的唯一标识;
- 数据则是存放在具有特定地址的内存单元中的具体信息。
为了有效存取一个数据,除了需要知道位置信息,还需要有该数据的类型信息。因为只知道了地址信息,不知道大小是多少,我可以取四个字节当int型,取1个字节当char型。一般C语言上的地址是指带类型的地址。
指针即变量的地址
指针变量即存放变量地址的变量
在定义指针变量时必须指定基类型
* : 取值操作符(指针运算符、间接访问符), *p代表变量p指向的对象
& : 取址操作符,&a 是变量a的地址
int i=200;
printf("直接访问变量i的值:%d",i);
int* pointer; // 这里的 * 表明是指针变量
pointer = &a; // 这里的 & 是取址操作符, &a 整型变量a的地址
printf("间接访问变量i的值:%d\n",*pointer); // 这里的 * 是取值操作符
指针变量
定义
类型名 *指针变量名
float a=3.14;
float *pointer_1;
float *pointer_2=&a;
pointer_1=&a;
*pointer_1 = &a; '错误的',即pointer_1是变量a
在定义指针变量时,左侧应有类型名,否则就不是定义指针变量。指针变量依此从指针中获取变量的值
指针变量前面的 * 表示该变量是指针变量。指针变量名是pointer_1,而不是 *pointer_1
指针变量的赋值:pointer_1=&a;而不是 *pointer_1=&a; 后者代表是变量a,*代表的是取值操作符,取变量a地址上的值给pointer_1。会报错
指针变量只能存放地址 (指针)
引用
-
给指针变量赋值
p = &a; // 把a的地址 指针变量p的值就是变量a的地址,p指向a(即*p是a变量,相当于*p是变量a的别名)
-
引用指针变量指向的指针
printf("%d",*p); 以整数形式输出指针变量p所指向的变量的值 *p = 1; 将整数1赋给p当前所指向的变量
-
引用指针变量的值
printf("p=%p",p); 输出指针变量p的值,即指针变量所存储的内存地址
指针变量作函数参数
指针类型作为函数的参数,它的作用是将一个变量的地址传送到另一个函数中。这样就可以做到修改实参的值。(函数的形参是值传递的)。
C语言中实参变量和形参变量之间的数据传递时单向的 “值传递方式”,用指针变量作函数参数时同意要遵循这一规则,不能企图通过改变指针形参的值而是指针实参的值改变。但是可以改变实参指针变量所指变量的值
例如:
对输入的两个整数按大小顺序输出
#include<stdio.h>
int main() {
void swap(int* p1, int* p2);
int a=5, b=9;
int* pointer_1, * pointer_2;
pointer_1 = &a;
pointer_2 = &b;
if (a < b) {
swap(pointer_1, pointer_2); // 依然是值传递,传的是a和b的地址
}
printf("max=%d,min=%d\n", *pointer_1, *pointer_2);
return 0;
}
void swap(int* p1, int* p2) {
int *temp;
temp = p1; // 这里的p1和p2只是swap函数里的局部变量,使用完swap函数后自动销毁。是实参创造出的副本,动副本的值并不改变原来的
p1 = p2;
p2 = temp;
}
结果:
max=5,min=9
指针引用数组元素
所谓数组元素指针就是数组元素的地址。
C语言中,数组名(不包括形参数组名)只代表数组中首元素(即序号为0的元素)的地址,不代表整个数组。
引用数组元素的方法:
-
下标法,如
a[3]
-
指针法,如 *(a+i) 或 *(p+i) 即通过指向数组元素的指针找到所需的元素。使用指针法能够使目标程序质量高(占内存少,运行速度快)
-
通过数组名计算数组元素地址,找出元素的指
-
用指针变量指向数组元素
-
三种方法比较,第一种和第二种执行效率相同。编译器将a[i]转换为 *(a+i) 处理的,所以方法一和方法二找数组元素费时较多;第三种方法用指针变量直接指向元素,不必每次都重新计算地址,像p++这样的自加操作时比较快的。
可以通过改变指针变量的值指向不同的元素,数组名不行,因为数组名代表数组首元素的地址,它是一个指针常量,它的值在程序运行期间是固定不变的
在使用指针变量指向数组元素时,应切实保证指向的数组中有效的元素,如果超出了数组的长度编译器不会报错,会产生不必要的麻烦。
指向数组元素的指针变量也可以带下标,如 p[i] ;因为编译器会自动把下标处理成 *(p+i) 的形式
下面两句等价
p = &a[0]; // 把数组a的首元素的地址赋给指针变量p
p = a;
int *p=&a[0];
等价于
int *p;
p=&a[0];
等价于
int *p=a;
指针的运算
当指针指向数组元素的时候,指针的加减运算才有意义。
当指针指向一个数组元素是:
-
加一个整数(用+或+=),如p+1,是只p当前所指的数组元素的下一个元素(的地址),执行p+1不是单纯的加一,而是加上一个数组元素所占的字节数;
-
减一个整数(用-或-=),如p-1;
-
自加运算,如p++,++p;
-
自减运算,如p–,–p;
两指针相减,如p1-p2,只有p1和p2都指向同一数组中的元素才有意义,代表着两个地址之差除以数组元素的长度,这样就可以知道两个元素的相对距离。
p1+p2;无意义
如果p的初值是&a[0],那么 p+i 和 a+i 就是数组元素 a[i] 的地址,*(p+i) 和 *(a+i) 就是所指向的数组元素 a[i] 。
使用指针引用数组元素的几个情况:
-
p++; // 是指针p指向下一个元素 *p; // 输出下一个元素的#### 用数组名作为函数的参数
-
*p++; 与 *(p++)
由于++与*同优先级,结合方向自右向左,等价于 *(p++); 先引用p的值,再p指向下一个元素。下面的两个代码片段是等价的 for(i=0;i<10;i++,p++) { printf("%d",*p); } for(i=0;i<20;i++) { printf("%d",*p++); }
-
*(++p) ,先使p指向下一个元素,再取 *p(获取该元素的值)
-
++(*p),表示p所指向的元素加一
-
如果p当前指向a数组中第i个元素a[i],则:
-
*(p–) 相当于 a[i–]
-
*(++p) 相当于 a[++i]
-
*(–p) 相当于a[–i]
-
// 输出数组a的前100个元素
p=a;
while(p<a+100) {
printf("%d ",*p++);
}
// 或
p=a;
while(p<a+100) {
printf("%d ",*p);
p++;
}
扩展: ++i 和 i++ 的用法
i++ : 先使用 i 的值,在使 i 自增1
++i : 先自增1,在使用 i 的值(此时的 i 的值比原先多1)
*(p++) : 先使用 *p 的值,再使p指向下一个元素
*(++p) : 先使p指向下一个元素,再使用 *p 的值
用数组名作函数的参数
当用数组名作为参数时,如果形参数组中元素的值发生改变,那么实参数组元素的值也发生改变。
实参数组名代表该数组首元素的地址,而形参是用来接受从实参传递过来的数组首元素的地址。实际上,C编译器将形参数组名作为指针变量来处理。例如:
foo(int arr[],int n) {......}
相当于
foo(int *arr,int n) {......}
常用这种方向通过调用一个函数来改变实参数组的值。
实参书数组名代表一个固定的地址,是指针常量,而形参数组名不是一个固定的地址,按照指针变量处理。
例如:
// 当用数组名作为参数时,如果形参数组中元素的值发生改变,那么实参数组元素的值也发生改变。
#include<stdio.h>
void func(int arr[], int n);
int main() {
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
func(arr, 10);
return 0;
}
void func(int arr[],int n) {
printf("%d\n", *arr);
arr = arr + 3;
printf("%d\n", *arr);
}
如果有一个实参数组,要想再函数中改变此数组中元素的值,实参与形参的对应关系有以下4种情况。
-
形参和实参都用数组名
可以认为在函数调用期间,形参数组与实参数组公用一段内存单元。int main() { int a[10]; foo(a,10) } int foo(int x[],int n){......}
-
实参用数组名,形参用指针变量
int main() { int a[10]; foo(a,10) } int foo(int *x,int n){......}
-
实参形参都用指针变量
int main() { int a[10],*p=a; foo(a,10) } int foo(int *x,int n){......}
-
实参为指针变量,形参为数组名
int main() { int a[10],*p=a; foo(a,10) } int foo(int x[],int n){......}
如果用指针变量作实参,必须先使指针变量有明确的值,指向一个已有的对象
通过指针引用多维数组
例如二维数组a[3][4],a[1]代表这是第二列,是一个一维数组,是第二列第一个元素的首地址;a[1][1] 代表的是第二行第二列的元素。
a[1] 和 *(a+1) 等价
a[i] 和 *(a+i) 等价
a[0]+1 和 *(a+0)+1 代表着是第一列的第二个元素的
地址
,等价于&a[0[1]*(a+1)+2 ≠ *(a+1+2),后者等于 *(a+3) 是第四列首元素的
地址
;前者是 a[1][2] 的地址
,等于&a[1][2]
输出二维数组的有关数据(地址和元素的值)
#include <stdio.h>
int main() {
int a[3][4] = { 1,3,5,7,9,11,13,15,17,19,21,23 };
printf("%p,%p\n", a, *a); // 0行起始地址和0行0列元素的地址
printf("%p,%p\n", a[0], *(a + 0)); // 0行0列元素地址
printf("%p,%p\n", &a[0], &a[0][0]); // 0行起始地址和0行0列元素的地址
printf("%p,%p\n", a[1], a + 1); // 1行0列元素地址和1行的起始地址
printf("%p,%p\n", &a[1][0], *(a + 1) + 0); // 1行0列元素地址
printf("%p,%p\n", a[2],*(a+2)); // 2行0列元素的地址
printf("%p,%p\n", &a[2],a+2); // 2行起始地址
printf("%d,%d\n", a[1][0],*(*(a+1)+0)); // 1行0列元素的值
printf("%d,%d\n", *a[2],*(*(a+2)+0)); // 第2行0列元素的值
return 0;
}
二维数组名(如a)是指向行(一维数组)的。因此 a+1 中的 ”1“ 代表一行中全部元素所占的字节数。
一维数组名(如 a[0])是指向列元素的值。a[0]+1 代表 a 中一个元素所占的字节数。
例如:a 和 a+1 是指向行的指针,在它们面前加一个 * 就是 *a 和 *(a+1) ,它们就成为列的指针,分别
指向
a数组0行0列元素和1行0列元素。在指向列的指针前面加 & ,就成为了指向行的指针,如 &a[0] 与 &*a与a等价,都指向二维数组的0行。
在指向列的指针前面加 * 就是获取该行该列的元素的值,如 *(*a+1)+1就是1行1列元素的值。
反正就是,不要简单地认为 *(a+i)是 a+i 所指单元中的内容。在一维数组中是正确的,但在二维数组中 a+i 不是指向具体存储单元的而是指向行(即一维数组,也可看做一维数组首元素的地址)。
在二维数组中, a+i 、 a[i] 、*(a+i) 、&a[i]、&a[i][0]的值是向相等的,即它们代表同一地址,但基类型不同。
#include<stdio.h>
int main() {
void print_array2(int a[3][4]);
void print_array22(int a[][4], int r);
void print_array23(int a[], int r, int c);
void print_array1(int a[], int n);
int a[3][4] = { {1,3,5,7},{2,4,6,8},{1,2,3,4} };
print_array2(a);
print_array22(a, 3);
print_array23(a, 3, 4);
print_array1(a[0], 4); // a[0]代表的是第一列的第一个元素的地址
print_array1(*(a + 1), 4); // a[1]与*(a+1)等价,同理a[i]和*(a+i)是等价的
printf("%d\n", *(a[1] + 2));
printf("%d\n", *(*(a + 1) + 2)); // *(a+1)+2=a[1]+2=&a[1][2]是第二列的第三个元素的`地址`
printf("%d\n", a[0][0]);
return 0;
}
/// 打印二维数组
void print_array2(int a[3][4]) {
printf("{");
for (int i = 0; i < 3; i++) {
printf(" { ");
for (int j = 0; j < 4; j++) {
printf("%d ", a[i][j]);
}
printf("}");
}
printf(" }\n");
}
void print_array22(int a[][4], int r) {
printf("{");
for (int i = 0; i < r; i++) {
printf(" { ");
for (int j = 0; j < 4; j++) {
printf("%d ", a[i][j]);
}
printf("}");
}
printf(" }\n");
}
void print_array23(int a[], int r,int c) {
printf("{");
for (int i = 0; i < r; i++) {
printf(" { ");
for (int j = 0; j < c; j++) {
printf("%d ", *(a+i*c+j));
}
printf("}");
}
printf(" }\n");
}
/// 打印一维数组
void print_array1(int a[],int n) {
printf(" { ");
for (int j = 0; j < n; j++) {
printf("%d ", a[j]);
}
printf("}\n");
}
指向多维数组的元素的指针变量
-
指向数组元素的指针变量
有一个 3x4 的二维数组,要求用指向元素的指针输出二维数组各元素的值#include<stdio.h> int main() { int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; int* p; for (p = a[0]; p < a[0] + 12; p++) { if ((p - a[0]) % 4 == 0) { printf("\n"); } printf("%4d", *p); } printf("\n"); p = &a; // 等价与 p=a;p=a[0]; printf("a[1][3]=%d\n", *(p + 1 * 4 + 3)); // a[i][j]的相对位置计算公式:i*m+j;m为二维数组的列数 return 0; }
-
指向由m个元素组成的一维数组的指针变量
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值警告
#include<stdio.h>
int main() {
int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int(*p)[4],i,j; // 注:(*p)[4] ≠ *p[4];前者代表`指针变量`指向包含4个整型元素的一维数组,后者是`指针数组`
// 可以把它看作是int a[4];*p=a;
p = a;
printf("please enter row and colum:");
scanf("%d %d", &i, &j);
printf("a[%d][%d]=%d\n", i, j, *(*(p + i) + j));
return 0;
}
return 0;
}
指针引用字符串
举例
通过指针变量访问整型数据
#include<stdio.h>
int main() {
int a = 100, b = 10;
int* pointer_1, * pointer_2;
pointer_1 = &a;
pointer_2 = &b;
printf("a=%d b=%d\n", a, b);
printf("*pointer_1=%d *pointer_2=%d\n", *pointer_1, *pointer_2);
printf("pointer_1=%p pointer_2=%p\n", pointer_1, pointer_2); // 存储的是内存单元的地址,不包括类型信息
return 0;
}
输入a和b两个整数,按先后大小的顺序输出a和b
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值警告
#include<stdio.h>
int main() {
int* p1, * p2, * p, a, b;
printf("please enter two interger numbers:");
scanf("%d %d", &a, &b);
p1 = &a;
p2 = &b;
if (a < b) {
p = p1;
p1 = p2;
p2 = p;
}
printf("a=%d,b=%d,\n", a, b);
printf("max=%d,min=%d\n", *p1, *p2);
return 0;
}
用函数处理且用指针类型的数据作为函数的参数,输入a和b两个整数,按先后大小的顺序输出a和b
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值警告
#include<stdio.h>
int main() {
void swap(int* p1, int* p2);
int a, b;
int* pointer_1, * pointer_2;
printf("please enter a and b:");
scanf("%d %d", &a, &b);
pointer_1 = &a;
pointer_2 = &b;
if (a < b) {
swap(pointer_1, pointer_2); // 依然是值传递,传的是a和b的地址
}
printf("max=%d,min=%d\n", a, b);
printf("max=%d,min=%d\n", *pointer_1, *pointer_2);
printf("a=%d,b=%d\n", a, b);
return 0;
}
void swap(int* p1, int* p2) {
int temp;
temp = *p1; // p1 <-> a
*p1 = *p2; // p2 <-> b
*p2 = temp;
}
输入3个整数a,b,c,要求由大到小的顺序将他们输出
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值警告
#include<stdio.h>
int main() {
void exchange(int* q1, int* q2, int* q3);
int a, b, c, * p1, * p2, * p3;
printf("please enter three number: ");
scanf("%d %d %d", &a, &b, &c);
p1 = &a;
p2 = &b;
p3 = &c;
exchange(p1, p2, p3);
printf("the order is: %d,%d,%d\n", a, b, c);
return 0;
}
void exchange(int* q1, int* q2, int* q3) {
void swap(int* p1, int* p2);
if (*q1 < *q2) {
swap(q1, q2);
}if (*q1 < *q3) {
swap(q1, q3);
}if (*q2 < *q3) {
swap(q2, q3);
}
}
void swap(int* p1, int* p2) {
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
有一个整型数组a,有10个元素,要求输出数组中的全部元素
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值警告
#include<stdio.h>
/*
通过指针引用数组元素的三种方法
- 下标法
- 通过数组名计算数组元素地址,找出元素的指
- 用指针变量指向数组元素
*/
int main() {
int a[10];
int i, * p;
printf("please enter 1o integer numbers: ");
//for ( i = 0; i < 10; i++) {
// scanf("%d", &a[i]); // 或用 a+i
// //scanf("%d", a + i);
//}
// 或
for (p = a; p < (a + 10); p++) {
scanf("%d", p);
}
for ( i = 0; i < 10; i++) {
printf("%d ", a[i]); // 数组元素用数组名和下标表示
}
printf("\n");
for (i = 0; i < 10; i++) {
printf("%d ", *(a + i)); // 通过数组名和元素序号计算元素地址,再找该元素
}
printf("\n");
for (p = a; p < (a + 10); p++) {
printf("%d ", *p); // 用指针指向当前的数组元素
}
printf("\n");
return 0;
}
错误分析:通过指针变量输出整型数组a的10个元素
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值
#include<stdio.h>
int main() {
int* p, i, a[10];
p = a;
printf("please enter 10 integer numbers: ");
for ( i = 0; i < 10; i++) {
scanf("%d", p++);
} // 此时p指向a的最后一个元素,需要使p重新指向数组元素a[0]
//p = &a[0];
for ( i = 0; i < 10; i++,p++) {
printf("%d ", *p);
}
printf("\n");
return 0;
}
// 输入:0 1 2 3 4 5 6 7 8 9
// 输出:-858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460
将数组a中n个整数按相反顺序存放,三种方式
#include<stdio.h>
int main() {
void inv(int x[], int n);
void inv1(int *x, int n);
int i, a[10] = { 3,7,9,11,0,6,7,5,4,2 },*p=a; // 如果用指针变量作实参,必须先使指针变量有明确的值,指向一个已有的对象
printf("the orginal array:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
inv(a, 10); // 数组名作为实参
printf("the array has benn inverted:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
inv1(a, 10); // 数组名作为实参
printf("the array has benn inverted:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
inv1(p, 10); // 指针变量作为实参
printf("the array has benn inverted:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
void inv(int x[], int n) { // 用数组名作为形参
int temp, i, j, m = (n - 1) / 2;
for (i = 0; i <= m; i++) {
j = n - 1 - i;
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}
void inv1(int *x, int n) { // 用指针变量作为形参
int *p,temp, *i, *j, m = (n - 1) / 2;
i = x; j = x + n - 1; p = x + m;
for ( ; i <= p; i++, j--) {
temp = *i;
*i = *j;
*j = temp;
}
}
用指针方法对10个整数按由大到小顺序排序
#include<stdio.h>
int main() {
void sort(int x[], int n); // 用数组名作为形参,用a[i](下标法)引用数组元素
void sort1(int x[], int n); // 用指针变量作形参,用*(p+i)引用数组元素
int i, * p, a[10];
p = a;
printf("please enter 10 integer numbers:");
for (int i = 0; i < 10; i++) {
scanf("%d", p++);
}
p = a; // 使p指向数组首元素
sort1(p, 10);
for ( p=a, i = 0; i < 10; i++) {
printf("%d ", *p);
p++;
}
printf("\n");
return 0;
}
void sort(int x[], int n) {
int i, j, k, t;
for (i = 0; i < n - 1; i++) {
k = i;
for (j = i + 1; j < n; j++) {
if (x[j] > x[k]) {
k = j;
}
if (k != i) {
t = x[i];
x[i] = x[k];
x[k] = t;
}
}
}
}
void sort1(int *x, int n) {
int i, j, k, t;
for (i = 0; i < n - 1; i++) {
k = i;
for (j = i + 1; j < n; j++) {
if (*(x + j) > *(x + k)) {
k = j;
}
if (k != i) {
t = *(x + i);
*(x + i) = *(x + k);
*(x + k) = t;
}
}
}
}
visual studio调试功能尝试
#include<stdio.h>
int main() {
void inv(int x[], int n);
void inv1(int *x, int n);
int i, a[10] = { 3,7,9,11,0,6,7,5,4,2 };
printf("the orginal array:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
inv(a, 10);
printf("the array has benn inverted:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
inv1(a, 10);
printf("the array has benn inverted:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
void inv(int x[], int n) {
int temp, i, j, m = (n - 1) / 2;
for (i = 0; i <= m; i++) {
j = n - 1 - i;
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}
void inv1(int *x, int n) {
int *p,temp, *i, *j, m = (n - 1) / 2;
i = x; j = x + n - 1; p = x + m;
for ( ; i <= p; i++, j--) {
temp = *i;
*i = *j;
*j = temp;
}
}