12 KiB
1. 准备知识
1.1 内存地址
-
字节:字节是内存的容量单位,英文称为 Byte,一个字节有8比特位,即 1Byte = 8bits 1B = 8b
-
地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址。
1.2 基地址
-
单字节数据:对于单字节数据而言,其地址就是其字节编号。
-
多字节数据:对于多字节数据而言,其地址是其所有字节地址中编号最小的那个,称为基地址。
1.3 取址符
-
每个变量都是一块内存,都可以通过取址符 & 获取其地址
-
注意:&符号的左边有操作数则表示位与运算符,若左边没有操作数则表示取地址符
int main(int argc, char *argv[])
{
int a=110;
printf("整型变量a的地址是:%p\n", &a);
char b='a';
printf("字符型变量b的地址是:%p\n", &b);
float c=3.14;
printf("浮点型变量c的地址是:%p\n", &c);
printf("整型变量a的地址长度是: %ld\n", sizeof(&a));
printf("字符型变量b的地址长度是:%ld\n", sizeof(&b));
printf("浮点型变量c的地址长度是:%ld\n", sizeof(&c));
return 0;
}
-
总结
-
虽然不同类型的变量的内存尺寸不同,但是他们的地址尺寸(地址编号的位数)却是相同的(地址的尺寸与系统的字长相关,32位系统地址尺寸为32位,64位系统地址尺寸为64位)
-
不同的地址虽然表示形式一样,但他们所代表的内存尺寸(在内存中所占内存大小)和类型都不相同,因此相同形式的地址编号在逻辑上要严格进行区分
2.指针入门
2.1指针概念
由于翻译问题及口语表达的习惯,在日常表述中,指针会有以下两种含义
- 指地址
int a;
&a; // 我们可以说&a指向a的地址
- 指指针变量
int *p; // 可以用于指向int类型数据的地址
-
号左右两边都有操作数,表示两数相乘(ab)
-
*号左右两边都有操作数,且左操作数数一个数据类型表示定义一个指针变量(int *p)
-
*号左边没有操作数,表示取值操作(*p)
2.2指针的定义
- 用于存放数据类型变量的地址的变量称为指针
int *p1; // 用于存储int类型数据的地址,p1被称为int型指针或整型指针
char *p2; // 用于存储char类型数据的地址,p2被称为char型指针或字符型指针
double *p3; // 用于存储double类型数据的地址,p3被称为double型指针或浮点型指针
对于int *p;在32位系统下的理解如下:
- 注意:指针用于存放地址的变量,由于在相同的系统位数下地址的尺寸相同,故指针的尺寸相同
- 指针变量的内存尺寸只于系统字长相关,与指针的类型无关。
2.3指针的赋值
- 可将一个地址类型于指针类型相同的地址赋值这个指针
int a=100;
char b='A';
double c=3.14;
p1 = &a; // 将a的地址赋值给指针p1,它们类型必须相同
p2 = &b; // 将b的地址赋值给指针p2,它们类型必须相同
p3 = &c; // 将c的地址赋值给指针p4,它们类型必须相同
int *p4 = p1; // 使用整型指针变量p1值初始化赋值整型指针p4
int *p5;
p5 = p1; // 整型指针变量p1值赋值给整型指针p5
int *p6 = &a; // 使用a的地址初始化赋值整型指针变量p6
2.4指针的索引
- 所谓索引,指的是通过指针变量取得其指向的目标(访问指针变量中所存储的地址中的数据)
*p1 = 200;
*p2 = 'B';
*p3 = 6.62;
a = 10;
a = 10;
练习
编写一个函数实现两个数交换。
3.特殊指针
3.1野指针
- 指向一块未知区域的指针,称为野指针。野指针是危险的
-
危害
-
引用野指针,相当于访问了非法内存,常常会导致段错误(segmentatio fault)
-
引用野指针,可能会破坏系统关键数据,导致系统崩溃等严重后果。
-
产生原因
-
指针定义式未进行初始化
-
指针指向内存被系统回收
-
指针越界
-
如何防止
-
定义指针时进行及时初始化(定义时无法明确指向则赋值为NULL)
-
绝不引用已被回收的内存(回收内存后令指针指向NULL)
-
确认所申请的内存边界,谨防越界
3.2空指针
NULL // (void *)0;
-
很多情况下,我们不可避免的会遇到野指针,如刚刚定义指针无法立即为其分配一块内存时,又或者指针所指向的内存被释放了等等。
-
对于一个暂时无法让其指向一块合法内存的指针而言, 我们最好将其初始化为“空指针” , 即给他赋一个空值(零) , 让他指向零地址
// 刚刚定义指针无法明确其指向,让其指向零地址以保证安全
int *p = NULL;
char *q = NULL;
//指针指向的内存被释放,令指针立即指向零地址保证安全
char *k = malloc(2); // 给指针分配内存
free(k); // 释放内存
k = NULL;
3.3void指针
-
概念:所谓void指针也称为万能指针(泛型指针),无法明确指针所指向的目标的数据类型则可将这种指针定义为void类型
-
要点:
-
void型指针无法索引目标,必须将其转换为一个具体的类型指针方可索引目标。
-
void型指针不支持加减运算。
void只用于三种情况 定义指针 定义函数类型 表示函数的参数列表
void swap(void *a, void *b)
{
// int *p = a;
// int *q = b;
// *p^=*q;
// *q^=*p;
// *p^=*q;
*((int *)a) ^= *((int *)b);
*((int *)b) ^= *((int *)a);
*((int *)a) ^= *((int *)b);
}
3.4char型指针
- char型指针实质上与别的类型指针并无本质区别,但由于C语言中字符串以字符数组的方式进行存储,而数组在大多数场合又表现为指针,因此字符串在绝大多数场合下表现为char型指针
char *p = "abcd"; // p指向的目标是字符串常量的地址
*p = 'x'; // 不可以修改常量数据
/* 上面的写法语法上无错,但逻辑不允许,推荐下面的写法,编译器会帮助我们检查语法错误 */
const char *q = "abcd"; // q指向的目标是字符串常量的地址
*q = 'x';
3.5const型指针
-
const(只读)型指针有两种形式:①常指针(指针只读)②常目标指针(目标只读)
-
常指针:const修饰指针本身,表示指针变量本身无法修改(指针常量)
char * const p;
char a,b;
char * const p = &a;
// p = &b; // 错误,常指针不能修改其值
*p = 'S'; // 可通过指针对目标进行读写
printf("%c\n", *p);
- 常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标(常量指针)。
const char *q = &a;
char const *k = &a; // k和q等价
q = &b; // 指针指向的目标可以修改
k = &b;
*q = 'a'; // 错误,常目标指针无法写访问目标
printf("%c\n", *k); // 正确。常目标指针读可访问目标
-
常指针在实际应用中不常见。
-
常目标指针在实际应用中广泛可见,用来限制指针的读写权限
晚上作业
3.6函数指针
-
指向函数的指针称为函数指针
-
函数指针与普通指针本质上并无区别,只是在取址和索引时取址符&与索引符*均可省略
double fun(double a[], int len)
{
double max = a[0];
for(int i=0; i<len; i++)
{
max = max>a[i]?max:a[i];
}
double min = a[0];
for(int i=0; i<len; i++)
{
min = min<a[i]?min:a[i];
}
return max-min;
}
int main(int argc, char *argv[])
{
double num[] = {1.23, 3.21,1.010,2.63,8.52};
double (*p)(double [], int ) = &fun; // p是一个函数指针,指向一个返回值为double形参为一个double型地址与一个int型数据的函数
printf("%02f\n", (*p)(num, 5)); // 通过指针p访问目标
printf("%02f\n", fun(num, 5));
/* C语言中函数的名字代表函数的地址 */
double (*q)(double *, int) = fun;
printf("%02f\n", q(num, 5));
printf("%02f\n", fun(num, 5));
return 0;
}
-
注意:函数fun == &fun 数组array != &array
-
要点
-
函数指针是一类专门指向某种类型函数的指针
-
函数的类型(返回值与参数列表)不同,所需要的函数指针也不同(指针的类型由函数的返回值与参数列表共同构成)。
-
函数的类型判定方法是在函数声明式中去除掉用户标识符名称,剩下的既是函数类型
3.7多级指针
-
若一个指针变量p1存储的地址是普通变量a的地址,则p1就称为一级指针
-
若一个指针变量p2存储的地址是指针变量p1的地址,则p2就称为二级指针
-
以此类推,p2就被称为多级指针
int a;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
4.指针的运算
-
指针的运算:加与减(+、-、++、--)
-
两个指针运算中'+'无意义,两个指针相加没有实际意义;两个指针做减法运算,结果可以得到两个地址间的偏移量(步长)
int a,b;
int *p = &a;
int *q = &b;
p+q; // 得到一个未知的新地址并无意义
p-q; // 得到两个地址间的偏移量,即从a的地址到b的地址间的距离(步长)
-
单指针的加法指的是指针自增运算或加上某个数值,表示指针指向的目标地址向上移动(向内存高地址移动)若干步长(步长与种指针的类型相关,一步为数据类型的内存尺寸)
-
单指针的减法指的是指针自减运算或减去某个数值,表示指针指向的目标地址向下移动(向内存低地址移动)若干步长(步长与种指针的类型相关,一步为数据类型的内存尺寸)
int a = 100;
int *p = &a; // 指针p指向a地址
int *k1 = p+2; // 向上移动2个步长
int *k2 = p-3; // 向下移动3个步长
- 要点:数据类型不同步长也不同,如:char *p; p++;与int *q; q++; 中p移动一步等于内存的一个字节而q移动一步等于内存的四字节。
int array[10] = {1,2,250,4,270,6,7,8,9,10};
int *p = array;
for (int i = 0; i < 10; i++)
{
printf("%d\t", *p);
p++; // 每次移动一步长(4字节)
}
printf("\n");
char *q=(char *)array;
for (int i = 0; i < 10; i++)
{
printf("%d\t", *(int*)q);
q+=4; // 每次移动一步长(1字节)
}
printf("\n");
练习
-
编写代码通过指针实现将两个字符串进行拼接(不要使用系统库函数)
-
如:char s1[20] = "hello"; char s2[20] = "world";调用自己写的函数后得到一个新的字符串"helloworld"
-
编写函数实现将将整型数组中重复的数字进行剔除,剔除后数组的元素个数要发生变化。
-
如:int array[] = {1,2,3,3,4,3,4,5,5};调用函数后变为{1,2,3,4,5};











