2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 【C语言指针】 指针+-整数 指针-指针 解引用 指针数组 二级指针 结构体声明 初始化 传参

【C语言指针】 指针+-整数 指针-指针 解引用 指针数组 二级指针 结构体声明 初始化 传参

时间:2019-07-14 20:05:16

相关推荐

【C语言指针】 指针+-整数 指针-指针 解引用 指针数组 二级指针 结构体声明 初始化 传参

目录

一、指针1、指针是什么1.1、一个单元1个字节2、指针和指针类型2.1、指针类型的意义① 解引用② + -整数例:把每个整形里放1每个字节里放1总结:3、野指针3.1、野指针成因① 指针未初始化② 指针越界访问③ 指针指向的空间释放3.2、如何规避野指针二、指针运算1、指针+-整数1.1、把数组每个元素赋成01.2、指针大小2、指针-指针指针-指针 得到的数字的绝对值是指针和指针之间元素的个数指针-指针求元素个数3、指针的运算关系代码1 从后往前改代码2 从前往后改总结三、指针和数组1、数组和指针的区别:2、数组名2.1、数组名地址 数组地址四、二级指针1、什么是二级指针2、二级指针的运算 两层解引用五、指针数组1、指针数组指针还是数组-数组2、地址解引用六、结构体1、结构体的声明结构的基础知识结构的声明2、结构成员的类型3、结构体变量的定义和初始化3.1、创建结构体类型变量3.2、结构体类型重命名-typedef3.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 5float 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, "200205" };Stu s2 = {"李四", 30, "200216" };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是在传参的瞬间开辟空间

栈区先使用高地址空间再使用低地址空间

结论:

结构体传参的时候,要传结构体的地址

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。