1.指针的理解:指针变量用来保存变量的地址
PP这个指针指向P这个变量:写做: int *PP =&P;
p为普通变量,存储值;pp为指针变量,存储地址。
2.指针的类型
int a= 10; int *p =&a; | double d = 3.14; double* p2 = &d; | char c ='d'; char* p3=&c; |
指针存储首字节地址 从首字节开始取4个字节 | 指针存储首字节地址 从首字节开始取8个字节 | 指针存储首字节地址 从首字节开始取1个字节 |
指针的步长为4 | 指针的步长为8 | 指针的步长为1 |
例:
//指针类型不同,指针的步长就不同(char* 步长为1 int* 步长为4)
int main()
{
char c = 'a';
char* p = &c;
printf("%d\n", p); //打印首地址:1834227
printf("%d\n", p + 1); //打印首地址的下一位:1834228(+1)
int a = 10;
int* p1 = &a;
printf("%d\n", p1); //打印首地址:1834200
printf("%d\n", p1 + 1); //打印首地址的下一位:1834204(+4)
}
int main()
{
int arr[] = { 1,2,3,4 };
int* p = &(arr[0]);
printf("%d,%d\n", *p, p); // 1,4651776
printf("%d,%d\n", *(p+1), p+1); // 2,4651780
printf("%d,%d\n", *(p+2), p+2); // 3,4651784
printf("%d,%d\n", *(p+3), p+3); // 4,4651788
}
2.1指针的强制转化
给地址重新赋值(一次性):
int main()
{
int a = 100;
int* ip = &a;
*ip = 0; //把0一次性赋值到四个字节(解引用)(通过指针间接修改值)
printf("%d",a); //输出为0
}
给地址重新赋值(分多次):
int main()
{
int a = 100;
int* ip = &a;
char* cp = (char*)ip; //指针的强制转换
//逐个字节去赋值
*cp = 0;
*(cp + 1) = 0;
*(cp + 2) = 0;
*(cp + 3) = 0;
printf("%d", a); //输出为0
}
3.指针的内存大小:指针无论什么类型,统统都是四个字节(32位)
int main()
{
int a = 10;
int* p = &a;
printf("%d", sizeof(int*)); //输出结果为4
}
4.野指针与空指针
野指针(悬挂指针):空间被回收了,指针还指向原来的空间,赋值给了一个不是自己空间的地址
空指针:指针没有指向
int* return_temp_value()
{
int p = 100;
return &p;
}
int main()
{
int* p2 = return_temp_value(); //由于局部变量在函数结束时要回收空间,所以此时P2失去指向,为野指针
p2 = NULL; //将野指针p2置为空,成为空指针,避免指针指向非法内存
}
5.指针与数组
5.1数组名就是一个指针,但是该指针不能改变指向
int main()
{
int a = 10;
int arr[] = { 10,20,30,40 };
printf("%d", *arr); //输出结果为10(数组名就是一个指向首元素的指针)
}
5.2当数组名作为函数参数的时候,会被编译器转换为指针
void print_array(int arr[])
{
int len = sizeof(arr) / sizeof(arr[0]); //int*长度为4,int 也为4
printf("%d", len);
}
int main()
{
int arr[] = { 10,20,30,40 };
printf("%d\n", sizeof(arr) / sizeof(arr[0])); //输出为4(16/4)
print_array(arr); //输出为1(4/4)(当数组名作为函数参数的时候,会被编译器转化为指针)
}
6.指针与字符串
6.1对于字符串常量的内存分配
int main()
{
char s[] = "hello world!";
s[0] = 'e';
printf("%s", s);
/*内部操作:编译器先给字符串常量分配内存(只读)
然后将常量拷贝到字符串数组的内存中(可读可写)
然后在字符串数组中进行修改,最后输出eello world */
}
;错误写法:
解释:由于局部变量在函数结束时,内存会回收,因此该空间不能再使用:
正确写法:
解释:S这段内存也会在程序结束时销毁,只不过在销毁之前先把地址返回给外部变量,而字符串常量的地址没有被销毁。
6.2编译器会把字符串常量当作地址来处理
int main()
{
const char* s1 = "hello world"; //在我们看来是hello world,但是在编译器看来它只是一个字符串的地址
printf("%s", s1); //输出hello world
s1 = "say hi"; //s1可以通过修改地址指向来改变值
}
7.指针数组
7.1普通数组与指针数组的差异
//普通数组:优点:数据独立
//缺点:无法修改变量的值;在数据较大的情况下,拷贝时间较长
int main()
{
int a = 10;
int b = 20;
int c = 30;
int arr[] = { a,b,c }; //拷贝一份数据到数组的地址内
arr[0]=100;
printf("%d",a); //输出为10(改变数组地址中的值而不是a中地址的值)
}
//指针数组:数组中存储地址,可以通过数组元素修改外部变量
//优点:由于存储的是地址,拷贝时间大大缩短(每次只拷贝四个字节)
//缺点:数组数据与外部数据关系太紧密(数据独立性太差)
void print_array(int* arr[], int len)
{
for (int i = 0; i < len; i++)
{
printf("%d\n", *(arr[i])+1);
}
}
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[] = { &a,&b,&c };
print_array(arr, sizeof(arr) / sizeof(arr[0])); //输出11 21 31
}
7.2 字符串指针数组
int main()
{
const char* s = "hello world"; //编译器将字符串自动分配地址去指向"hello world"
//字符串指针数组:
const char* names[] = { "Trump","Pelosy","Obama"}; //三个指针,共12个字节
//遍历输出
int len = sizeof(names) / sizeof(names[0]); // 12/4
for (int i = 0; i < len; i++)
{
printf("字符串为:%s" "字符串大小为:%d" "首字符是:%c\n", names[i],strlen(names[i]),*(names[i]));
//names[i]的类型为char* ,*(names[i])的类型为char
}
}
8.const与指针
8.1 const可以提醒编译器限制变量的更改,但可以通过间接的方法(修改地址)来修改值
ps.const修饰全局变量时,不能通过指针去修改
int main()
{
const int c = 100;
int* p = (int*)&c;
*p = 200;
printf("%d", c); //输出结果为200
}
8.2const修饰指针
8.2.1指针常量:指针的地址不可修改,指向的值可以修改
int a = 10, b =20;
int *const p = &a;
p = &b; //×
*p = 30; //√
8.2.2常量指针:指针地址可以修改,指向的值不可更改
int a = 10, b =20;
const int* p = &a;
p = &b; //√
*p = 30; //×
8.3.3 如果既不能修改指向,也不能修改指向的值
int a = 100;
const int* const p =&a;
9.指针的使用场景
9.1利用址传递来提高效率
void print_value(int* p) {
printf("%d", *p);
}
int main()
{
int a = 100;
print_value(&a);
}
//编译器对数组的优化:每次传参时将数组名退化成指针,提高传递效率
void printArr(int* arr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d\n", arr[i] );
}
}
int main()
{
int arr[] = { 10, 20,30,40 };
printArr(arr, sizeof(arr)/sizeof(arr[0]));
}
9.2交换俩个变量的值
void swap_value(int* p,int *q)
{
int temp = *p;
*p = *q;
*q = t;
}
int main()
{
int a = 10;
int b = 20;
swap_value(&a, &b);
printf("a=%d,b=%d", a, b);
}
9.3得到多个返回值(通过指针间接修改变量的方式)
#include <iostream>
#define ARR_LENGTH 10
void get_value(int arr[],int length,int* p_max,int* p_min,int* p_avg)
{
//求最大值,最小值,以及平均值
int max = 0;
int min = 0;
int avg = 0;
int sum = 0;
for (int i = 0; i < length; i++)
{
if (arr[i] > max) {
max = arr[i];
}
if (arr[i] < min) {
min = arr[i];
}
sum += arr[i];
}
avg = sum / length;
//返回三个值
* p_max = max;
* p_min = min;
* p_avg = avg;
}
//打印函数
void print_arr(int arr[],int length)
{
for (int i = 0; i < length; i++)
{
printf("%d\t", arr[i]);
}
printf("\n");
}
int main()
{
//设置随机数种子
srand( (unsigned int) time(NULL));
int arr[ARR_LENGTH] = { 0 };
//产生十个随机数(1-100)赋值到数组中
for (int i = 0; i < ARR_LENGTH; i++)
{
arr[i] = rand() % 100 +1;
}
int max_value = 0;
int min_value = 0;
int avg_value = 0;
print_arr(arr, ARR_LENGTH);
get_value(arr, ARR_LENGTH, &max_value, &min_value, &avg_value);
printf("%d\n%d\n%d\n", max_value, min_value, avg_value);
}
10.多级指针的理解
10.1多级指针的定义:
(1)P左边的星代表它是指针变量,int右边的星代表它指向的数据类型:
例如对int* *p 的解释:*p表示它是一个指针,int* 表示它指向的是一级指针。
(2)解引用一次就是减少一颗星(*),取地址一次就是增加一颗星(*):
对P1进行解引用,类型为int,对P2进行解引用,类型为 int* ,依次类推
PS.由于访问层次的增加,因此尽量减少使用多级指针。
10.2多级指针的应用:
(1)通过二级指针改变一级指针的指向:
int main()
{
int a = 10;
int b = 20;
//修改变量的值:直接修改
//int* p = &a;
//p = &b;
//printf("%d", *p); //输出20
//用高一级的指针去间接修改第一级指针的值
int* p1 = &a;
int* *P2 = &p1; //增加“&”要加星
*P2 = &b; //解引用要降星
printf("%d", *p1); //输出20
}
(2)
void point_to_string(char* s)
{
s="hello world";
}
int main(void)
{
char* p =NULL;
point_to_string(p);
printf("%s",p); //输出结果为null,因为"hello world" 赋值给了S并不是P
}
可以用二级指针进行修改:
void point_to_string(char* *s) //0x666
{
*s="hello world";
}
int main(void)
{
char* p =NULL; //0x666
point_to_string(&p);
printf("%s",p); //输出结果为hello world,通过二级指针修改了一级指针的值
}
(3)
//编写函数,打印字符串指针数组
void print_strings(char* *names, int length){
for (int i = 0; i<length; i++) {
printf("%s\n",names[i]);
}
}
/*数组名是指向数组首元素的指针,数组的names的首元素是char* 类型
数组名是指向char* 指针的指针:char* *
*/
int main(void)
{
char* names[]={"Trump","Obama","Biden"};
print_strings(names,3);
}
11.指针练习
(1)指针实现字符串拷贝
//拷贝字符串
void copy_string(char* src, char* dst)
{
if (src == NULL){
printf("src指针为空\n");
return;
}
if (dst == NULL) {
printf("dst指针为空\n");
return;
}
char* s = src; //用新变量保存指针,原来的指针保持原样
while (*s !='\0') //字符的最后一位都为\0
{
*dst = *s; //char*类型每次向后移动一位
++s;
++dst;
}
*dst = *s; //将字符串最后的\0拷贝到目标空间
}
int main()
{
char* src = "hello world!"; //源字符串
char dst[128] = { 0 }; //目标字符串
copy_string(src, dst);
printf("%s", dst);
}
(2)指针实现字符串拼接
//把原字符串拼接到目标字符串之后
void str_cat(char* src, char* dst) {
if (src == NULL) { return; }
if (dst == NULL) { return; }
char* p = src;
while (*p != '\0') //先找到目标字符串\0的位置
{
++p;
}
while (*dst != '\0') { //将src从\0之后开始赋值
*p=*dst;
++p;
++dst;
}
*p = '\0'; //末尾补0
}
int main()
{
char s1[128] = "hello"; //源字符串"hello\0"
char s2[] = "world"; //目标字符串"world\0"
str_cat(s1, s2);
printf("%s\n", s1);
}