我使用VS CODE+MSYS2的编译环境进行学习,想使用VS CODE进行C/C++代码编写的小伙伴参考这篇文章进行环境配置VS Code 配置 C/C++ 编程运行环境(保姆级教程)
一、指针的引入
指针==地址
#include <stdio.h>
int main() {
int a = 10;
printf("The value of a is: %d\n", a);
printf("The address of a is: %p\n", &a);
printf("The value of a is: %d\n", *(&a));//取值运算符,把后面内存地址内的数据“取出来”
return 0;
}
变量:int a=10; (四要素:类型int、变量名a、内存地址、值10)
变量访问的两种方式:
1、通过变量名访问:
printf("The value of a is: %d\n", a);
2、通过地址访问:
&取地址运算符 *将地址内的值读出运算符printf("The value of a is: %d\n", *(&a));
二、指针变量的引入
指针变量==存放地址的变量
#include <stdio.h>
int main() {
//什么是整型变量,存放整型数的变量
//什么是字符型变量,存放字符型数据的变量
//什么是指针变量,存放指针的变量
//指针==地址:指针变量就是存放地址的变量
int a = 10;
int *p;//这里*是一个标识符,告诉系统我是一个指针变量,用来保存别人的地址,和14行运算符不同
p=&a;//p=a的地址
printf("The value of a is: %d\n", a);
printf("The address of a is: %p\n", &a);
printf("The value of a is: %d\n", *(&a));//取值运算符,把后面内存地址内的数据“取出来”
printf("pointer-The value of a is: %d\n", *p);
return 0;
}
如何定义一个指针变量—— * 的标识作用——只用于指针变量定义或者声明的时候
如何使用一个指针变量—— * 的运算作用——把地址内的数据取出
#include <stdio.h>
int main() {
int a = 0x1234;
int *i = &a;
char *c = &a;
printf("*i:a=%x\n", *i);
printf("*c:a=%x\n", *c);
printf("*i:addr=%p\n", i);
printf("*c:addr=%p\n", c);
printf("++i=%p\n", ++i);
printf("++c=%p\n", ++c);
}
int类型占用4个字节(byte),一个字节占用8位(bit)————32bit
char类型占用1个字节,一个字节占用8位————8bit
三、为什么要使用指针
1、场景一之变量值交换
#include <stdio.h>
int main(){
int a=10;
int b=20;
int temp;
printf("before:");
printf("a=%d\t",a);
printf("b=%d\n",b);
temp=a;
a=b;
b=temp;
printf("after:");
printf("a=%d\t",a);
printf("b=%d\n",b);
return 0;
}
当我们想把main函数里面的值交换封装到函数里面却发现交换后的a和b的值并没有改变
#include <stdio.h>
void changeData(int a,int b){
int temp;
temp=a;
a=b;
b=temp;
}
int main(){
int a=10;
int b=20;
printf("before:");
printf("a=%d\t",a);
printf("b=%d\n",b);
changeData(a,b);
printf("after:");
printf("a=%d\t",a);
printf("b=%d\n",b);
return 0;
}
原因是应为数据发生交换的内存空间和main函数里变量的内存空间不在同一内存空间 ,解决办法将changeData函数的参数变为指针,调用changeData函数时将变量a和b的地址传入
#include <stdio.h>
void changeData(int *a,int *b){
int temp;
temp=*a;
*a=*b;
*b=temp;
}
int main(){
int a=10;
int b=20;
printf("before:");
printf("a=%d\t",a);
printf("b=%d\n",b);
changeData(&a,&b);
printf("after:");
printf("a=%d\t",a);
printf("b=%d\n",b);
return 0;
}
2、场景二之指针指向固定的内存地址
#include <stdio.h>
int main(){
unsigned int *p=(unsigned int *)0x0019FF11;
printf("p = 0x%p\n",p);
return 0;
}
四、通过指针引用数组
1、定义一个指针变量指向数组
在C语言中,数组名(不包括形参的数组名,形参数组并不占用实际的内存单元)和数组中的首元素都可以代表数组的首地址
2、指针偏移遍历数组
我们之前访问数组元素用的都是下标法printf("arr[0]=%d\n",arr[0]); 系统在使用数组下标对数组成员进行访问时开销比较大,指针的访问效率远远大于数组名的访问效率但普通工程代码量小影响微乎其微。
在数组中所有元素的地址是连续的,第一个元素是a,那么第二个就是a+1,第三个就是a+2......但这个1并不是数组一而是字节数,比如int类型的数组第二个元素就比第一个元素的地址多了四个字节(32位)参考本文 二、指针变量的引入理解
每次重新编译运行后数组的首地址都会改变,因为局部数组是放在栈区的,由编译器自动进行分配和释放
#include <stdio.h>
int main(){
int arr[3]={1,2,3};
printf("arr address:%p\n",arr);
printf("arr[0] address:%p\n",&arr[0]);
int *p,*q;
p=arr;
q=&arr[0];
printf("a[0]=%d\t",*(p));//指针变量p存放了数组的首地址,通过取值符号*将地址内的值取出
printf("a[0] address:%p\n",p);
printf("a[1]=%d\t",*(p+1));
printf("a[1] address:%p\n",p+1);
printf("a[2]=%d\t",*(p+2));
printf("a[2] address:%p\n",p+2);
printf("a[0]=%d\t",*(q));
printf("a[1]=%d\t",*(q+1));
printf("a[2]=%d",*(q+2));
return 0;
}
通过for循环遍历可节省代码量
#include <stdio.h>
int main(){
int arr[3]={1,2,3};
int len=sizeof(arr)/sizeof(int);//或者int len=sizeof(arr)/sizeof(arr[0])
printf("arr address:%p\n",arr);
printf("arr[0] address:%p\n",&arr[0]);
for(int i=0;i<len;i++){
printf("arr[%d]=%d\t",i,arr[i]);
}
printf("\n\n");
int *p,*q;
p=arr;
q=&arr[0];
for(int i=0;i<len;i++){
printf("arr[%d]=%d\t",i,*(p+i));
printf("address=%p\n",p+i);
}
printf("\n");
for(int i=0;i<len;i++){
printf("arr[%d]=%d\t",i,*(q+i));
printf("address=%p\n",q+i);
}
return 0;
}
3、指针当做数组名来用
4、指针常量和指针变量的区别
int *p是指针变量,它所保存的地址是可变的
数组名arr是指针常量,它的地址是确定的
#include <stdio.h>
int main(){
int arr[3]={1,2,3};
int *p=arr;
for(int i=0;i<3;i++){
printf("%d ",*p++);
}
/*
for(int i=0;i<3;i++){
printf("%d ",*arr++);//编译不通过,指针常量
}
*/
for(int i=0;i<3;i++){
printf("%d ",*(arr+i));
}
printf("\n\n");
return 0;
}
5、sizeof计算大小的区别
在32位操作系统中指针占4字节,64位操作系统中指针占8字节
6、小练习
(1)通过指针的方式实现数组的初始化和遍历
#include <stdio.h>
void initArray(int *p,int len);
void printArray(int *p,int len);
int main(){
int arr[3];
int len=sizeof(arr)/sizeof(int);
initArray(arr,len);
printArray(arr,len);
return 0;
}
void initArray(int *p,int len){
for(int i=0;i<len;i++){
printf("please enter %d number:",i+1);
scanf("%d\n",p++);
}
}
void printArray(int *p,int len){
for(int i=0;i<len;i++){
printf("arr[%d]=%d\t",i,*(p+i));
//printf("arr[%d]=%d\t",i,*p++)
}
}
(2)实现数组翻转
#include <stdio.h>
void Array1(int *p,int len);
void Array2(int *p,int len);
void printArray(int *p,int len);
int main(){
int arr1[5]={5,4,3,2,1};
int arr2[6]={6,5,4,3,2,1};
int len1=sizeof(arr1)/sizeof(int);
int len2=sizeof(arr2)/sizeof(int);
printf("before:\n");
printArray(arr1,len1);
printf("\n");
printArray(arr2,len2);
printf("\nafter:\n");
Array1(arr1,len1);
printArray(arr1,len1);
printf("\n");
Array2(arr2,len2);
printArray(arr2,len2);
return 0;
}
void Array1(int *p,int len){
int temp;
for(int i=0;i<(len/2);i++){//前2个数,中间那个数不变
for(int j=len-i-1;j<=len-i-1;j++){//后2个数
/*形式1*/
temp=p[i];
p[i]=p[j];
p[j]=temp;
/*形式2*/
// temp=*(p+i);
// *(p+i)=*(p+j);
// *(p+j)=temp;
}
}
}
void Array2(int *p,int len){
int temp;
for(int i=0;i<=(len/2);i++){//前3个数
for(int j=len-i-1;j<=(len-i-1);j++){//后3个数
/*形式1*/
// temp=p[i];
// p[i]=p[j];
// p[j]=temp;
/*形式2*/
temp=*(p+i);
*(p+i)=*(p+j);
*(p+j)=temp;
}
}
}
void printArray(int *p,int len){
for(int i=0;i<len;i++){
printf("arr[%d]=%d\t",i,*(p+i));
//printf("arr[%d]=%d\t",i,*p++)
}
}
五、二维数组
有一个二维数组a,它有3行4列:int a[3][4]={{1,3,5,7},{2,4,6,8},{1,2,3,4}};
a是二维数组名,a数组包含3行即3个行元素:a[0], a[1], a[2]。而每个行元素又是一个一位数组,它包含4个元素(即4个列元素)。例如a[0]所代表的一位数组包含4个元素:a[0][0], a[0][1], a[0][2],a[0][3];可以认为二维数组是数组的数组,即二维数组a是由3个一维数组所组成的。
a[0], a[1], a[2]既然是一维数组名,而数组名又代表数组首元素地址,因此a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0]。也就是说a[1]的值就是&a[1][0],a[2]d的值就是&a[2][0]。
a==&a[0]==&a[0][0]
a+1==&a[1]==&a[1][0]
虽然说 a, a[0]都是代表第一个一维数组的首地址&a[0][0],但是在去值的时候我们发现*a是无效的。在C语言中,二维数组名实际上是一个指向数组首行首元素的一维数组指针。所以,对于数组 int a[3][4]
:
a
表示整个二维数组,等价于*(a+0)
或者&a[0][0]
,即指向第一个一维数组(行)的首地址。*a
解引用a
,得到的是a
所指向的内容,也就是a[0]
,它是一个包含四个整数的一维数组。
因此,*a
实际上等于 a[0]
,它们都是指向同一块内存区域,即数组的第一行 [1, 3, 5, 7]
的起始地址。但在表达式中直接写 *a
并不能获得具体某个元素的值,因为 *a
还是一个数组,不是单个整数。如果你想访问第一个元素,可以写作 (*a)[0]
,这等同于 a[0][0]
,结果就是 1。