指针
在计算机中,内存空间的最小单位为字节,操作系统会为每一个字节内存空间编号,并且这个编号在当前程序中是唯一的。
读写内存的两种方式
- 通过变量名读写内存
变量本质上是一块有名字的内存空间,可以通过变量名读写对应的内存空间
- 通过内存地址读写内存
在计算机内存中,每一个字节内存空间都有一个编号,这个编号被称为内存地址。通过该地址可以读写对应的内存空间。而变量的本质是一块有名字(也有编号)的内存空间,在32位系统下,这个编号是一个四字节的整数。
&变量名。由于这种方式得到的内存地址就是变量所对应的内存空间地址,又是通过变量名得到的,因此可以称为“变量地址”,变量地址本质上就是内存地址。
#include<stdio.h>
int main(void)
{
int i = 10;
printf("%p",&i);//输出i的内存地址
getchar();
return 0;
}
用于保存内存地址的变量,称为指针变量。在 C 语言程序中不仅变量有类型,数据也是有类型的,例如: 1(整数)、 3.14(浮点数)、 ’c’(字符),需要使用与之匹配的类型变量进行保存。同理,内存地址也是一种数据,这种数据都是指针类型,因此需要指针类型变量来保存这种数据。
指针变量的定义与初始化
定义指针变量的一般形式为:类型名 *变量名; (也可以写做 类型名* 变量名,但是不推荐,*加在变量名前,更能看出其为指针),类型名表示该指针变量只能保存该类型变量的地址, *表示该变量是指针变量只能保存地址数据,变量名即该变量的名称。
指针变量和普通变量初始化方式相同,可以在变量定义时初始化,也可以先定义后初始化。
例如:
int a=10;
int *p_a=&a; //定义 int 指针变量 p_a,并将变量 a 地址赋值给 p_a
或者
int a=10;
int *p_a; //先定义 int 指针变量 p_a
p_a=&a; //然后将变量 a 地址赋值给变量 p_a
整数变量 a 的地址赋值给指针变量 p_a,就认为 p_a 指向了变量 a 。变量 a 中存储的是整数 10,而变量 p_a 中存储的是变量 a 的地址。想要访问数据 10,必须先找到指针变量 p_a,通过变量 p_a 中的数据&a,再找到变量 a,最后访问数据 10。
输出p,才是输出地址值
输出*p,是输出p对应的内存地址中的值
改变p对应的内存地址中的值,并不会改变p对应的内存地址
#include<stdio.h>
int main(void)
{
int i = 999;
int *p = &i;
printf("%p\n", &i);//十六进制输出i的地址值,会补齐0
printf("%d\n", &i);//十进制输出i的地址值,不会补齐0
printf("%d\n", p);//十进制输出i的地址值,不会补齐0
printf("%d\n", *p);//输出999,即*p指向的内存地址中的值
printf("%d\n", i);//输出999,即i的数值
*p = 888;
printf("%d\n", *p);//输出888,即*p指向的内存地址中的值
printf("%d\n", p);//十进制输出i的地址值,不会补齐0
printf("%p\n", &i);//十六进制输出i的地址值,会补齐0
printf("%d\n", i);//输出888,即i的数值
getchar();
return 0;
}
在 C 语言中,函数参数不仅可以是字符型、整型、浮点型……等,还可以是指针类型,作用是将变量地址传递给函数形参。
主要目的就是:函数内部修改外部变量的值。
#include<stdio.h>
int fun(int *i){
*i = 6;
printf("%d\n", *i);//输出6
printf("%d\n", i);//输出变量i的内存地址,要注意这里只有指针类型的变量,这里的i不是main方法中的i,而是传入的形参
}
int main(void)
{
int i = 10;
printf("%d\n", i);//输出10
fun(&i);
printf("%d\n", i);//输出6
getchar();
return 0;
}
通过指针,来交换两个变量的值
#include<stdio.h>
int fun(int *i, int *j){
int temp = *i;
*i = *j;
*j = temp;
}
int main(void)
{
int i = 10;
int j = 20;
fun(&i, &j);
printf("%d\n", i);
printf("%d\n", j);
getchar();
return 0;
}
scanf函数
scanf 函数原型: int scanf(const char * _Format, …)
头文件:#include<stdio.h>
参数列表:
_Format :格式控制字符串,与 printf 函数中格式控制作用相同。
… :地址列表,由若干个地址组成。
功能: 获取用户按键输入的数据,并以指定格式写入到变量、或数组中。
#include<stdio.h>
int main(void)
{
int a = 2;
printf("请输入第一个数:\n");
scanf("%d", &a);//从键盘输入一个整数,写入变量 a 中
printf("变量a的值为%d",a); //输出变量a的值
getchar();//接收使用 scanf 时按下的回车键
getchar();//程序暂停,等待用户输入
return 0;
}
从键盘获取多个数据时,相邻数据之间可以使用空格、回车、tab 键作为两个数据之间的分隔符。
如果在 scanf 函数中的格式控制字符串中除了占位符之外,还有其他字符,则在输入时也必须在对应的位置上输入相同的字符。
如下,scanf 函数中,格式控制字符串中为"%d+%d",则键盘上输入数字的时候,数字中间也要加上+
int main(void)
{
int a;
int b;
printf("请输入两个数:\n");
scanf("%d+%d", &a, &b);
printf("变量a,b的值为%d,%d",a,b); //输出变量a,b的值
getchar();
getchar();
return 0;
}
使用 scanf 获取字符串时,只需传入字符数组名即可,取地址符&可以省略不写。
数组与指针
数组本质上是一片连续的内存空间,每个数组元素都对应一块独立的内存空间,它们都有相应的地址。因此,指针变量既然可以指向变量,也就可以指向数组元素。
在 C 语言中数组可以看作是相同类型变量的集合。通俗点讲,数组中每个元素类型都是相同的。数组本质上是一片连续的内存空间,数组元素又可以看作是单独的内存空间,因此每个数组元素也都有自己的内存空间地址,简称数组元素地址。
输出p_a1和p_a2的值,可以发现,两个值相差总为4,就是一个int类型的数所占的4个字节数
#include<stdio.h>
int main(void)
{
int a[5]={1,2,3,4,5};
int *p_a1; //定义指向int变量的指针变量p_a1
int *p_a2;
p_a1=&a[0]; //把数组中第一个元素的地址赋值给指针变量
p_a2=&a[1];
printf("%d\n", p_a1);
printf("%d", p_a2);
getchar();
return 0;
}
在 C 语言中,数组名与数组首元素地址等价。
指针加减法
指针本质上就是内存地址,在 32 位操作系统下,内存地址只是 4 字节的整数。既然是整数,就可以进行加、减、乘、除……等算术运算。不过需要注意,在 C 语言中一般只讨论指针加、减运算,乘、除等其他算术运算是没有意义。
在实际开发中,指针加、减多用于数组(或者连续内存空间)。当指针变量 p 指向数组元素时,p+1 表示指向下一个数组元素,p-1 表示指向上一个数组元素。注意加减运算不是 “移动一个字节”,而是移动一个“单元”,对于 int 来讲一个单元是 4 个字节。
#include<stdio.h>
int main(void)
{
int a[5]={1,2,3,4,5};
int *p_a1; //定义指向int变量的指针变量p_a1
int *p_a2;
p_a1=&a[0];
p_a2= p_a1 + 2;
printf("%d\n", p_a1);//输出数组第一个元素的内存地址
printf("%d\n", p_a2);//输出数组第三个元素的内存地址,与第一个元素的内存地址相差8
printf("%d\n", *p_a2);//输出数组第三个元素的值
getchar();
return 0;
}
总结:
1) 两个指针之间的减法表示:相差的单元的个数。
2) 两个指针之间的加法,没有意义;
3) 指针+普通整数表示指针向后挪 n 个单元;指针-普通整数表示指针向前挪 n 个单元;
数组也可以作为函数参数进行传递,但是数组做函数参数时,是没有副本机制的,只能传递地址。也可以认为,数组做函数参数时,会退化为指针。
#include<stdio.h>
//给函数传递的数组类型的形参,等同于传递此数组的指针
void getSize(int p_nums[])
{
int size=sizeof(p_nums);
printf("size=%d\n",size);//输出4,int类型的指针所占字节数
printf("%d\n", &p_nums);//输出的是形参对应的指针的地址值(此地址值和数组的地址值是不相同的)
printf("%d\n", &p_nums[0]);//输出的是实参对应的数组的地址值(数组第一个元素的地址值)
}
int main(void)
{
int nums[5]={1,2,3,4,5};
int size=sizeof(nums); //计算数组 nums 总字节数
printf("%d\n", &nums[0]);//输出的是实参对应的数组的第一个元素的地址值
printf("%d\n", &nums);//输出的是实参对应的数组的地址值(数组第一个元素的地址值)
printf("size=%d\n",size);//输出20
getSize(nums);
getchar();
return 0;
}
由于数组做函数参数时,会退化为指针,导致无法在被调函数中计算传入的数组大小以及长度。为了解决这种问题,规定数组做函数参数时,必须传入数组长度
除了在声明数组时候 sizeof(数组名)和 sizeof(指针变量名)的不同,其他时候“数组名” 和“指针变量名”用法都是一样的
#include<stdio.h>
int main(void)
{
int nums[]={1,2,3,4,5};
int *p_nums = nums;//直接将数组赋值给指针类型的变量p_nums,此时p_nums保存的是数组的地址值
printf("%d\n", p_nums[4]);//输出数组的第五个元素
printf("%d\n", nums[4]);//输出数组的第五个元素
getchar();
return 0;
}
指针所占字节数和电脑的操作系统位数有关,32位的系统,指针是4个字节
#include <stdio.h>
int main(void)
{
int a=1;
char b='a';
float c=1.0;
void *p;
p=&a;
printf("a的地址为:0x%x,其字节数为:%d\n",p,sizeof(p));//输出4
p=&b;
printf("b的地址为:0x%x,其字节数为:%d\n",p,sizeof(p));//输出4
p=&c;
printf("c的地址为:0x%x,其字节数为:%d\n",p,sizeof(p));//输出4
getchar();
return 0;
}
void* 类型的指针
void*类型的指针,能够接收任意类型的地址,但是不可以直接进行解引用的操作,需要强转为某个指针类型。void*类型的指针也不能直接进行加减的操作
字符串处理函数
strcpy
函数原型: char *strcpy(char *dest, char *src);
头文件: #include<string.h>
参数列表: dest:目标字符数组。 src:源字符串。
功能: 把 src 指向的字符串复制到 dest 指向的字符数组中。
返回值: 返回 dest 指向的内存地址。
src数组和dest数组是两个不同的数组,有不同的内存地址。假如src字符串中有\0,那么strcpy函数就只会拷贝\0之前的字符串。
#include <stdio.h>
#include <string.h>
int main(void)
{
char *src="test";
char dest[10]={0};
strcpy(dest, src);
printf("%s\n",src);
printf("%s\n",dest);
getchar();
return 0;
}
strcpy只能实现字符串的拷贝,函数参数和返回值都只为char类型
memcpy
函数原型: void *memcpy(void *dest, const void *src, int size);
头文件:#include<string.h>
参数列表: dest:目标地址空间 src: 源地址空间 size: 要复制的字节个数。
功能: 从 src 指向的内存空间起始位置开始,拷贝 size 个字节到 dest 指向的内存空间中。
返回值: 返回 dest 指向的目标地址。
memcpy除了字符串可以拷贝,其他的数据类型也能拷贝,比如结构体类型等
#include <stdio.h>
#include <string.h>
int main(void)
{
int src[] = {1,2,3,4,5};
int dest[10] = {0};
int i;
//8表示8个字节,即只拷贝src数组的前两位给dest数组
memcpy(dest,src,8);
//for循环中的i也要预先定义
for(i = 0; i < 5; i++){
printf("%d\n", dest[i]);
}
getchar();
return 0;
}
strcmp
字符串比较函数
函数原型:int strcmp(char *str1,char *str2);
头文件:#include<string.h>
参数列表: str1:字符串 1, str2:字符串 2
功能: 从 str1 与 str2 的首字符开始逐个比较(比较字符的 ASCII 码),直到出现不同的字符或遇到’\0’为止。
返回值: (1) 字符串 str1 小于 str2,返回值为负数。 (2) 字符串 str1 等于 str2,返回值为 0。 (3) 字符串 str1 大于 str2,返回值为正数。所谓字符串的大小,就是当出现不同的字符时,比较此字符的ASCII码,此字符ASCII码大的,字符串大。
ANSI 标准规定,返回值为正数、负数、0 。而确切数值根据不同的C会有变化,不是所有的此函数的返回值都是0,1,-1。
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "abcd";
char str1[] = "abxd";
int i = strcmp(str,str1);
printf("%d", i);//输出-1,表示str比str1要小
getchar();
return 0;
}
stricmp
忽略大小写的字符串比较函数
strcmp 与 stricmp 用法基本相同,stricmp 只是忽略了大小写进行比较而已。
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "abcd";
char str1[] = "ABCD";
int i = stricmp(str,str1);
printf("%d", i);//输出0,两字符串相等
getchar();
return 0;
}