C指针细节
C语言中的*和&具体分析
在C语言中的用法
指针的声明:int *p 或 int* p; 读法:p是指向一个整数类型的指针。
复合指针: int **p;或int** p; 读法 p是一个指向一个指向整数类型的指针的指针。
解引用: x=*p 把指针p指向的值赋值给x。
int a = 10;
int *p = &a;
*p是一个int类型的指针
&p表示的是一个地址,&是取地址符(号)
printf(“%d\n”, a);
printf(“%d\n”, &a);
printf(“%d\n”, b);
printf(“%d\n”, *b);
结果:
10
6487620
6487620
10
变量a 本质上代表一个存储单元。CPU通过该存储单元的地址访问该存储单元中的数据。
所以a本来代表两个值:存储单元的地址和储单元中的数据。
于是就有了二异性。为了消除这种二义性,C语言规定a表示存储单元中的数据,&a表示存储单元的地址
a存储单元中的数据可以是一个普通数值,也可以是另一个存储单元的地址,比如:a = &b; 语句就是将b的存储单元的地址存入a存储单元中。
C语言规定a代表a中存储的地址对应的存储单元中的数据,也就是访问a就等于访问b,于是*a提供了通过a访问b中的数据的手段。
a表示a对应的存储单元中的数据。
&a表示a对应的存储单元的地址。
a表示:首先,要求a对应的存储单元中的数据 一定是另一个存储单元的地址。
于是,a表示另一个存储单元中的数据
当a声明的类型是int时,a中存储的是一个整数数值,通过a可以访问(读取或修改)这个数值。
当a声明的类型是int *时,a中存储的是一个存储单元的地址,而该存储单元中存储的数据是一个整数数值;通过*a可以访问(读取或修改)这个数值。a == &*a 都是该存储单元的地址。
当a声明的类型是int**时,a中存储的是一个存储单元的地址,而该存储单元中存储的数据是另外一个存储单元的地址,另外这个存储单元中存储的是一个整数数值;通过**a可以访问(读取或修改)这个数值。
…
最后,在C语言里地址叫指针。还有,在C语言中的数组本质上其实也是指针,即:*a 等同于 a[]。
//先解引用第一个地址(*phead),然后得到一个值(值是一个地址)类型是 PHONE *,所以是PHONE*( *phead )
int init(PHONE **head)
{
PHONE *newnode = (PHONE*)malloc(sizeof(PHONE));
if(NULL == newnode)
{
return -1;
}
newnode->next = NULL;
*head = newnode;
return 0;
}
/*
定义结构指针变量的一般形式
PHONE* head -->结构名 * 结构指针变量名
通过结构指针间接访问成员值
访问的一般形式:
(*结构指针变量). 成员名 或 结构指针变量 -> 成员名
如:
(*head).name
head->name
*/
int create(PHONE *head)
{
PHONE *p;
p = head;
PHONE *newP = (PHONE*)malloc(sizeof(PHONE));
if(newP == NULL)
{
return -1;
}
printf("\33[0;34m请输入信息\33[0m\n");
srand((unsigned)time(NULL));
newP->id = rand() %100;
printf("\33[0;34m名字\33[0m\n");
printf("name :\n");
scanf("%s",newP->name);
printf("\33[0;34m电话号码\33[0m\n");
scanf("%s",newP->phone_number);
printf("\33[0;34m家庭地址\33[0m\n");
scanf("%s",newP->home_address);
printf("\33[0;34m公司号码\33[0m\n");
scanf("%s",newP->company_number);
newP->next = NULL;
while(head->next != NULL)
{
head = head->next;
}
head->next = newP;
printf("\33[0;32m create! \33[0m\n");
getchar();
getchar();
head = p;
}
C指针细节
悬空指针
C语言中的指针可以指向一块内存,如果这块内存稍后被操作系统回收(被释放),但是指针仍然指向这块内存,那么,此时该指针就是“悬空指针”。
例子:
void *p = malloc(size);
assert(p);
free(p); //现在p就是一个悬空指针
“悬空指针”会引发不可预知的错误,而且错误一旦发生,难以定位。这是因为在 free(p) 之后,p 指针仍然指向之前分配的内存,如果这块内存暂时可以被程序访问并且不会造成冲突,那么之后使用 p 并不会引发错误。
在实际开发中,为了避免出现”悬空指针“,在释放内存之后,常常会将指针 p 赋值为 NULL:
void *p = malloc(size);
assert(p);
free(p);
// 避免“悬空指针”
p = NULL;
这么做的好处是一旦再次使用被释放的指针 p,就会立刻引发“段错误”,方便查找错误点。
段错误是计算机软件运行过程中可能出现的一种特殊错误情况。当程序试图访问不允许访问的内存位置,或试图以不允许的方式访问内存位置(例如尝试写入只读位置,或覆盖部分操作系统)时会发生段错误。
野指针
“悬空指针”是指向被释放内存的指针,“野指针”则是不确定其具体指向的指针。“野指针”最常来自于未初始化的指针,例如:
void *p;
// 此时 p 是“野指针”
因为“野指针”可能指向任意内存段,因此它可能会损坏正常的数据,也有可能引发其他未知错误,所以C语言中的“野指针”危害性甚至比“悬空指针”还要严重。
在实际开发中,定义指针时,一般都要尽量避免“野指针”的出现(赋初值):
void *p = NULL;
void *data = malloc(size);
C语言运算符
内存地址的概念
多级指针案例 取出子函数中临时变量的地址
指针变量的赋值只能赋予地址, 决不能赋予任何其它数据。在C语言中, 变量的地址是由编译系统分配的,C语言中提供了地址运算符&来表示变量的地址。一个指针变量可以认为它是在一个.c文件中是全局的。
int *p = &a;
*指针变量 的作用是获取指针变量指向的内存空间的内容
修改指针,需要用指针的指针
"*"的两种用法:
1)用于定义一个指针变量
2)存储指针变量储存的存储空间的内容
指针常见的应用场景
1)在函数中可以修改主调函数中变量的值
2)让函数可以有多个返回值
多级指针
int* p; int 类型的一级指针;
int** p2; int 类型的二级指针;
二级指针变量只能保存一级指针变量的地址;
有几个* 就是几级指针 int*** 三级指针。
通过int类型三级指针 操作int类型变量的值 ***p
#include <stdio.h>
void swap (int *p1,int *p2)
{
int t;
int*p;
t=*p1;
*p1=*p2;
*p2=t;
// p=p1;
// p1=p2;
// p2=p;
}
void swap2(int **m, int**n)
{
int*p;
p=*m;
*m=*n;
*n=p;
}
void main( )
{
//&取地址,*取值
int a=1,b=2,*p=&a,*q=&b;
printf("%d,%d,%d,%d\n",a,b,*p,*q);
printf("%d,%d,%d,%d\n",&a,&b,&(*p),&(*q));
swap(p,q);
printf("%d,%d,%d,%d\n",a,b,*p,*q);
printf("%d,%d,%d,%d\n",&a,&b,&(*p),&(*q));
swap2(&p,&q);
printf("%d,%d,%d,%d\n",a,b,*p,*q);
printf("%d,%d,%d,%d\n",&a,&b,&(*p),&(*q));
// ,
//1,2,1,2
// 6422300,6422296,6422300,6422296
// 2,1,2,1
// 6422300,6422296,6422300,6422296
// 2,1,1,2
// 6422300,6422296,6422296,6422300
}
int main(){
int number = 5;
int *ptr = &number;
printf("number's address = [%p]\n",&number);//number的地址
printf("number's value = [%d]\n",number);//number的值
printf("ptr's address:%p\n",&ptr);//(指针变量)ptr的地址
printf("ptr's value:%p\n",ptr);//ptr的值
printf("ptr pointing value:%d\n",*ptr);//ptr指向的变量的值
/*
number's address = [0061FF1C]
number's value = [5]
ptr's address:0061FF18
ptr's value:0061FF1C
ptr pointing value:5
*/
return 0;
}
//a = a | (1<<n); --> a |= (1<<n) 输出高电平
//b = b & ~(1<<n); --> a & = ~(1<<n) 输出低电平
/*
1 << n 向左移位 1左移n位 n << 1 n左移1位
* & 与 (都是1时,结果才为1) 1&1=1 1&0=0 0&0=0
*
* | 或 (只要有1,那么就是1) 1|1=1 1|0=1
*
* ^ 异或 (只要一样结果就是0) 0^0=0, 1^0=1, 0^1=1, 1^1=0
* ~ 取反
*
*
* */
#include<stdio.h>
#include<stdlib.h>
/**
main函数获取子函数中临时变量的地址
这其实还是值传递和引用传递的问题
*/
function(int** pointer) {
int i = 123;
*pointer = &i;
printf("i的地址%#x\n", &i);
}
main() {
int* pointer1;
function(&pointer1);
printf("pointer1的值%#x\n", pointer1);
system("pause");
}
i的地址0x1147410c
pointer1的值0x1147410c
联合体(共用体)
长度(大小)等于联合体中定义的变量当中最长的那个,联合体只能保存一个变量的值
联合体共用同一块内存,在嵌入式设备中起到节省内存的目的.
共用体也是用户自定义的数据类型,不过该类想中的所有成员共用一块内存,因此一个成员变量赋值,就等于所有成员都被赋予了相同的值。
#include<stdio.h>
#include<string.h>
union book
{
char name[10];
char price[10];
};//定义一个共用体,有2个成员,name和price,这2个成员共用一块内存,因此他们的地址相同
int main()
{
union book mybook;
strcpy(mybook.name,"呼啸山庄");
strcpy(mybook.price,"11元8角");
printf("name的地址 :%p\n",&mybook.name);
printf("price的地址:%p\n",&mybook.price);//输出结果显示它们的地址相同
printf("书名:%s\t",mybook.name);
printf("价格:%s\n",mybook.price);//由于price1被赋值时,覆盖了name1成员的值,因此他们的值也是一样的
return 0;
}
//name的地址 :0x7ffde4ac2a46
//price的地址:0x7ffde4ac2a46
//书名:11元8角 价格:11元8角
typedef void (*Fun) (void) 的理解——函数指针——typedef函数指针
//返回类型(*函数名)(参数表)
//定义一个函数指针pFUN,它指向一个返回类型为char,有一个整型的参数的函数
char (*pFun)(int);
//定义一个返回类型为char,参数为int的函数
//从指针层面上理解该函数,即函数的函数名实际上是一个指针,
//该指针指向函数在内存中的首地址
char glFun(int a)
{
cout << a;
//return a;
}
int main()
{
//将函数glFun的地址赋值给变量pFun
pFun = glFun;
//*pFun显然是取pFun所指向地址的内容,当然也就是取出了函数glFun()的内容,然后给定参数为2。
(*pFun)(2);
return 0;
}
typedef函数指针
//typedef 返回类型(*新类型)(参数表)
typedef char (*PTRFUN)(int);
PTRFUN pFun;
char glFun(int a){ return;}
void main()
{
pFun = glFun;
(*pFun)(2);
} </span>
typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,
这种函数以一个int为参数并返回char类型。后面就可以像使用int,char一样使用PTRFUN了。
第二行的代码便使用这个新类型定义了变量pFun,此时就可以像使用形式1一样使用这个变量了。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!