【C语言指针】 指针+-整数、指针-指针、解引用、指针数组、二级指针、结构体声明、初始化、传参
目录
- 一、指针
- 1、指针是什么
- 1.1、一个单元1个字节
- 2、指针和指针类型
- 2.1、指针类型的意义
- ① 解引用
- ② + -整数
- 例:把每个整形里放1
- 每个字节里放1
- 总结:
- 3、野指针
- 3.1、野指针成因
- ① 指针未初始化
- ② 指针越界访问
- ③ 指针指向的空间释放
- 3.2、如何规避野指针
- 二、指针运算
- 1、指针+-整数
- 1.1、把数组每个元素赋成0
- 1.2、指针大小
- 2、指针-指针
- 指针-指针 得到的数字的绝对值是指针和指针之间元素的个数
- 指针-指针求元素个数
- 3、指针的运算关系
- 代码1 从后往前改
- 代码2 从前往后改
- 总结
- 三、指针和数组
- 1、数组和指针的区别:
- 2、数组名
- 2.1、数组名地址 数组地址
- 四、二级指针
- 1、什么是二级指针
- 2、二级指针的运算 两层解引用
- 五、指针数组
- 1、指针数组指针还是数组-数组
- 2、地址解引用
- 六、结构体
- 1、结构体的声明
- 结构的基础知识
- 结构的声明
- 2、结构成员的类型
- 3、结构体变量的定义和初始化
- 3.1、创建结构体类型变量
- 3.2、结构体类型重命名-typedef
- 3.3、初始化
- 3.3.1、结构体嵌套初始化
- 4、结构体成员的访问
- 5、、结构体传参
- 传值 / 传址
- 结论
一、指针
1、指针是什么
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向
(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以
说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址
的内存单元
- 地址指向了一个确定的内存空间,所以地址形象地被称为指针
- int* pa = &a; pa是用来存放地址(指针),所以pa是指针变量
总结:
指针是个变量,用来存放内存单眼的地址。(存放在指针中的值都被当成地址处理)。
1.1、一个单元1个字节
问题:
一个小的单元到底是多大?(1个字节)
如何编址?
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)
产生的二进制序列有2^32
这样的二进制序列,如果作为内存单元的编号,2^32次方个地址,能够管理2的32次方个内存单元
//bit
//byte
//kb
//mb
//gb
//tb
//pb…
2^32bit -> 4,294,967,296 ->/8/1034/1024/1024 ->0.5gb
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,
所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
2、指针和指针类型
2.1、指针类型的意义
32位 指针变量大小都是4字节 为什么还要有这么多指针类型?
① 解引用
指针类型的意义1:
指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存大的大小)
char* 指针解引用访问1个字节
int* 指针解引用访问4个字节
int main()
{int a = 0x11223344; //8个二进制位是1字节//int* pa = &a;//*pa = 0; //改了4字节 (调试在内存中看)char* pc = &a;*pc = 0; //只改了1字节return 0;
}
② + -整数
指针类型的意义2:
指针类型决定了,指针+ -整数的时候的步长(指针±整数的时候,跳过了几个字节)
int* 指针+1 跳过了4个字节
char* 指针+1 跳过了1个字节
例:把每个整形里放1
int main()
{int arr[10] = { 0 }; //40字节int* p = arr; // arr数组名表示首元素地址 &arr[0] - int*int i = 0;for (i = 0; i < 10; i++){*(p + i) = 1; // 0x00 00 00 01//+1访问1个整形}return 0;
}
每个字节里放1
int main()
{int arr[10] = { 0 };char* pa = arr;int i = 0;for (i = 0; i < 10; i++){*(pa + i) = 1; //每次改1个字节}return 0;
}
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
3、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1、野指针成因
① 指针未初始化
int main()
{int* p; //没有初始化-里面放的是随机值*p = 20; //通过p中内存的随机数值作为地址,找到一个空间,//这个空间不属于我们当前的程序,就造成了非法访问,p就是野指针return 0;
}
② 指针越界访问
int main()
{int arr[10] = {0};int i = 0;int* p = arr;for (i = 0; i <= 10; i++) //0-10{//当指针指向的范围超出数组arr的范围时,p就是野指针*p = i;p++;// *(p++) = i}return 0;
}
③ 指针指向的空间释放
int* test() //返回类型int*
{int a = 10;return &a; //类型是int*
}int main()
{int* p = test(); //指针用p接收printf("%d\n", *p); //指针变量p 存test的返回值 调用test 创建a变量 放10 返回到p//出作用域 还给操作系统 但此时p还是存了地址 非法访问return 0;
}
内存中的栈区是从高地址到低地址使用,高地址使用完再使用低地址
3.2、如何规避野指针
- 初始化指针
int main() {int a = 10;int* p = &a; //明确地初始化,确定指向int* p2 = NULL; //不知道一个指针当前应该指向哪里时,可以初始化为NULL//NULL本质上是0 强制转化成了((void*)0)
}
小心指针越界
指针指向空间释放即置成NULL
避免返回局部变量的地址(避免指针指向的空间被释放)
指针使用之前检查有效性
int main()
{int* p2 = NULL;*p2 = 100; //err//0指向的空间是不允许被使用的if (p2 != NULL){*p2 = 100;}return 0;
}
二、指针运算
1、指针±整数
1.1、把数组每个元素赋成0
#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[0]; vp < &values[N_VALUES];) //指针比较大小
{//由下标的增长 地址由低到高*vp++ = 0; //++优先级高于* 但是后置++ 所以先解引用vp//指针=- float指针+1跳过一个float类型的变量 跳的是一个元素
}
1.2、指针大小
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[9];printf("%p\n", p);printf("%p\n", p-1); //差4 整形指针 4个字节
}
2、指针-指针
指针-指针 得到的数字的绝对值是指针和指针之间元素的个数
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", &arr[9] - &arr[0]); //9printf("%d\n", &arr[0] - &arr[9]); //=9//下标0-9之间有9个元素//指针-指针 得到的数字的绝对值是指针和指针之间元素的个数//指针-指针的前提是两个指针指向同一个区域return 0;
}
指针-指针求元素个数
int my_strlen(char* s)
{char* start = s;while (*s != '\0'){s++;}return s - start; //等找\0时是指向\0 减起始位置
}int main()
{char arr[] = "abcdef";int len = my_strlen(arr);printf("%d\n", len);return 0;
}
3、指针的运算关系
代码1 从后往前改
for (vp = &values[N_VALUES]; vp > &values[0];) //下标为5的地址放进vp
{*--vp = 0;//先-- 指向下标4 最后一个元素改成0//从最后一个元素向前改
}
代码2 从前往后改
for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
{*vp = 0;//最后一次把第一个元素改成0 vp-- 指向第一个元素之前一个float类型的位置
}
总结
//避免使用代码2的方法
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,
但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
三、指针和数组
1、数组和指针的区别:
数组 - 是一块连续的空间,放的是相同类型的元素
数组大小和元素类型,元素个数有关
int arr[10]
指针(变量) - 是一个变量,放地址
指针变量的大小 是4(32bit)/8(64bit)个byte
2、数组名
int main()
{int arr[10];//int* p = &arr[5]; //取出下标为5的元素的地址//int* q = &arr; // 取出整个数组的地址printf("%p\n", arr); //数组是首元素的地址printf("%p\n", &arr[0]); //首元素的地址int sz = sizeof(arr); //数组是首元素的地址 那为什么不是4/8 而是40//数组名确实是首元素的地址 但有两个例外//1. sizeof(数组名) - 这里的数字名不是首元素的地址,是表示整个数组的,这里计算的是是整个数组的大小,单位还是字节//2. &数组名 - 这里的数字名不是首元素的地址,是表示整个数组的,拿到的这个数组的地址return 0;
}
2.1、数组名地址 数组地址
int main()
{int arr[10] = { 0 };//arr - 数组名是首元素的地址//&arr[0] - 第一个数组的地址//&arr - 取出整个数组的地址//值一样 但意义不一样 //首元素地址和数组地址起始位置是一样printf("%p\n", arr);printf("%p\n", &arr[0]);printf("%p\n", &arr);printf("%p\n", arr);printf("%p\n", arr + 1); //整形指针+1 跳过一个整形printf("%p\n", &arr[0]);printf("%p\n", &arr[0] + 1);printf("%p\n", &arr);printf("%p\n", &arr + 1); //数组的地址+1 整形数组10元素 40字节return 0;
}
四、二级指针
1、什么是二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?这就是二级指针
二级指针:用来存放一级指针变量的地址
三级指针:用来存放二级指针变量的地址
int main()
{int a = 10;int* p = &a;int* * pp = &p; //pp就是二级指针//第二个*:pp是指针//int*:pp指向的p的类型是int*int** * ppp = &pp; //ppp就是三级指针//最后一个*:ppp是指针//int**:ppp指向的pp的类型是int**return 0;
}
2、二级指针的运算 两层解引用
int main()
{int a = 10;int* p = &a;int** pp = &a;**pp = 20; //*pp解引用找到p **pp找到aprintf("%d\n", a);return 0;
}
五、指针数组
1、指针数组指针还是数组-数组
int main()
{int arr[10]; //整形数组 - 存放整形的数组就是整形数组char ch[5]; //字符数组 - 存放字符的数组就是字符数组//指针数组 - 存放指针的数组就是指针数组//int* 整形指针的数组//char* 字符指针的数组int* parr[5]; char* pc[6]; //6个元素 每个元素类型是char* return 0;
}
2、地址解引用
int main()
{int a = 10;int b = 20;int c = 30;int* arr[3] = { &a, &b, &c };//int* pa = &a;//int* pb = &b;//int* pc = &c;int i = 0;for (i = 0; i < 3; i++){printf("%d\n", *(arr[i]));//对一个地址解引用 拿到的是这个地址指向的元素}return 0;
}
六、结构体
1、结构体的声明
结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体:为了可以描述复杂对象
复杂对象的属性不止一个 所以就有了不同的成员变量
结构的声明
struct Book //结构体类型
{//成员变量 可以是不同类型char name[20];char author[15];float price;
};
2、结构成员的类型
结构的成员可以是标量、数组、指针,甚至是其他结构体。
3、结构体变量的定义和初始化
3.1、创建结构体类型变量
struct Book
{char name[20];char author[15];float price;
}; b1, b2; //b1 b2是全局变量 - 内存的静态区struct Point
{int x;int y;
}; p1, p2; //p1 p2是全局变量int main()
{//创建结构体类型变量struct Book b; //局部变量 - 栈区struct Point p; //局部变量return 0;
}
3.2、结构体类型重命名-typedef
typedef struct Stu
{char name[20];int age;char id[20];
}Stu; //对struct Stu重命名 是类型 与struct Stu一样int main()
{struct Stu s1; //用struct StuStu s2; //更加简洁return 0;
}
3.3、初始化
int main()
{struct Stu s1 = { "张三", 20, "2020080205" };Stu s2 = { "李四", 30, "2020080216" };struct Book b;struct Point p = { 10, 20 };return 0;
}
3.3.1、结构体嵌套初始化
struct S
{int a;char c;double d;
};struct T
{struct S s; //用struct S类型 创建了struct S s变量char name[20];int num;
};int main()
{struct T t = { {100, 'c', 3.14}, "lisi", 30 };return 0;
}
4、结构体成员的访问
struct S
{int a;char c;double d;
};struct T
{struct S s; //用struct S类型 创建了struct S s变量char name[20];int num;
};int main()
{//初始化struct T t = { {100, 'c', 3.14}, "lisi", 30 };//访问printf("%d %c %f %s %d\n", t.s.a, t.s.c, t.s.d, t.name, t.num);//用结构体指针访问struct T* pt = &t;printf("%d %c %f %s %d\n", pt->s.a, pt->s.c, pt->s.d, pt->name, pt->num);//结构体变量本身用.找 //结构体指针用-> 当找到它的成员s是结构体变量时,用.找成员areturn 0;
}
5、、结构体传参
传值 / 传址
struct S
{int arr[100];int num;char ch;double d;
};//结构体传参
void print1(struct S ss) //传值调用
{printf("%d %d %d %d %c %lf", ss.arr[0], ss.arr[1], ss.arr[2], ss.num, ss.ch, ss.d);}//结构体地址传参
void print2(struct S* ps)
{printf("%d %d %d %d %c %lf", ps->arr[0], ps->arr[1], ps->arr[2], ps->num, ps->ch, ps->d);
}int main()
{struct S s = { {1,2,3,4,5}, 100, 'w', 3.14 };print1(s); //传结构体print2(&s); //传地址 4个字节 //只传了地址 减少空间浪费 传参压力减小return 0;
}
首选print2
函数传参的时候,参数是需要压栈的
函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,
参数压栈的系统开销比较大,所以会导致性能的下降。
结论
int Add(int x, int y)
{int z = 0;z = x + y;return z;
}int main()
{int a = 10;int b = 20;int c = 0;c = Add(a, b);return 0;
}
形参x y是在传参的瞬间开辟空间
栈区先使用高地址空间再使用低地址空间
结论:
结构体传参的时候,要传结构体的地址
【C语言指针】 指针+-整数、指针-指针、解引用、指针数组、二级指针、结构体声明、初始化、传参相关推荐
- C语言结构体的定义与使用、结构体数组、指向结构体的指针(有代码详解)
1.结构体的定义与使用 结构体是一种构造数据类型 把不同类型的数据组合成一个整体 结构体的定义形式: struct 结构体名{结构体所包含的变量或数组 }; 结构体是一种集合,它里面包含了多个变量或数 ...
- 详解多维数组与指针之间的关系
一维数组 先介绍一下简单的一维数组: 列如: int a[3] = {0,1,2}; [3]和类型int则明确表示编译器应该为这个栈分配多大的内存,也就是三个int大小! 在内存中示意图是: 在CPU ...
- 指针(*)、取地址()、解引用(*)与引用()
指针(*).取地址(&).解引用(*)与引用(&) C++ 提供了两种指针运算符,一种是取地址运算符 &,一种是间接寻址运算符 *. 指针是一个包含了另一个变量地址的变量,您可 ...
- 2020-09-25C++学习笔记引用2:二级指针形参与指针形参的使用方法和区别重难点,主看综合代码(1、指针形参2、引用形参3、二级指针形参4、指针引用形参)
2020-09-25C++学习笔记引用2:二级指针形参与指针引用搭配做形参的使用方法区别重难点,注意事项主看综合代码(1.指针形参2.引用形参3.二级指针形参4.指针引用形参) 1.指针引用 #inc ...
- 关于指针结构体的初始化
项目场景: 力扣141:循环链表的判定 在VS中初始化链表,涉及到节点结构体的初始化, 问题描述 在VS中初始化链表,涉及到节点结构体的初始化,一开始的写法: typedef struct ListN ...
- c语言 结构体声明和引用、,结构体的声明与自引用
今天上了数据结构课程的第一堂课,经常会看到下面这种语法:结构体有个成员变量是指向该结构体的指针,也就是自引用(self reference).翻看了下一章节内容,才知道这是链表的结构基础.平时C语言用 ...
- 《c语言从入门到精通》看书笔记——第11章 结构体和共用体
1.结构体: "结构体"是一种构造类型,它是由若干"成员"组成的,其中的每一个成员可以是一个基本数据类型或者有事一个构造类型. (1)声明结构体时使用的关键字是 ...
- go结构体初始化_go嵌套匿名结构体的初始化详解
go匿名结构体 嵌套匿名结构体的 示例代码片. type debugConfig struct { MaxQueueDepth int `json:"maxQueueDepth"` ...
- C语言结构体——指定初始化
概述 C 语言结构体指定初始化(Designated Initializer)实现上有两种方式: 一种是通过点号加赋值符号实现,即.fieldname = value, 另一种是通过冒号实现,即fie ...
最新文章
- SAP WM 通过2-Step Picking创建的TO之间的关联关系
- 你不知道的Node.js性能优化,读了之后水平直线上升
- centos 7 mysql图形界面_centos7-vnstat图形界面搭建
- WebService应用一例,带有安全验证
- 计算机网络协议和通信规则,计算机网络协议基本知识
- python中的装饰器和抽象类
- ansible 配置文件优先级
- 扎克伯格夏威夷州大肆买地引发当地不满 被批搞“新殖民主义”
- 速修复!Netgear交换机曝3个严重的认证绕过漏洞
- python实现协同过滤推荐算法完整代码示例
- 无效的列类型 || Mbatis-Plus链接oracle
- 红米K30Pro手机电路图纸 主板+小板电路原理图
- CCS错误解决:#10247-D null: creating output section “ramfuncs“ without a SECTIONS specification
- 政策利好市场需求双加持,粉笔科技双轨并进强势突围
- Java JUI打字小游戏项目
- OrCAD打开工程报错-ERROR(ORCAP-1653)
- 杭州再发力!余杭又添一区块链产业园,携手巴比特欲打造创新应用示范区
- python数据预测模型算法_Python机器学习 预测分析核心算法
- 动态设置根节点字体大小
- 硬核图书系列:《漫画算法:小灰的算法之旅》