一、指针
1.1、指针是什么
指针是什么?
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
#include <stdio.h>
int main()
{
int a = 10; //a是整形变量,占用四个字节的内存空间
int* pa = &a;
// pa 是一个指针变量,指针变量里面存放的是地址,通过这个地址可以找到一个内存单元
//口语中的指针是指针变量,是用来存放地址的变量
return 0;
}
指针变量 :
- 我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量。
#include <stdio.h>
int main()
{
int a = 10; //在内存中开辟一块空间
int* p = &a; //这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
//中,p就是一个之指针变量。
return 0;
}
总结 :
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
指针变量的大小 :
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
简单来说:
- 指针变量是用来存放地址的,地址是唯一标示一块地址空间的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节
1.2、指针和指针类型
1.2.1 指针的类型
这里我们在讨论一下:指针的类型 我们都知道,变量有不同的类型,整形,浮点型等。
那指针有没有类型呢? 准确的说:有的。
int num = 10;
p = #
要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢?
我们给指针变量相应的类型。
#include <stdio.h>
int main()
{
char* pc = NULL;
short* ps = NULL;
int* pi = NULL;
double* pd = NULL;
//sizeof返回值是无符号整形 unsiged int
printf("%zu\n", sizeof(pc)); // 4
printf("%zu\n", sizeof(ps)); // 4
printf("%zu\n", sizeof(pi)); // 4
printf("%zu\n", sizeof(pd)); // 4
return 0;
}
这里可以看到,指针的定义方式是: type + * 。
其实:
- char* 类型的指针是为了存放 char 类型变量的地址。
- short* 类型的指针是为了存放 short 类型变量的地址。
- int* 类型的指针是为了存放 int 类型变量的地址。
1.2.2 指针的意义
意义 1 :
#include <stdio.h>
//指针的意义
//十六进制
// 0 1 2 3 4 5 6 7 8 9 a b d e f
int main()
{
int a = 0x11223344;
//int* pa = &a;
//*pa = 0;
char* pc = (char*)&a;
*pc = 0;
//从下面图可以看出 a 从 44 33 22 11 在 int 类型中完全变成了 00 00 00 00
//而在 char 类型中变成了 00 33 22 11
//结论1 ;
//指针类型决定了指针在被解引用时访问几个字节
//如果 int* 的指针,解引用访问4个字节
//如果 char* 的指针,解引用访问1个字节
//double* 的指针,8个字节
return 0;
}
意义 2 :
注 :int 和 float 不可以混用**
1.3、野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.3.1 野指针的成因
1.3.1.1. 指针未初始化
#include <stdio.h>
int main()
{
int* p;
//p没有初识话,就意味着没有明确的指向
//一个局部变量不初始化,放的是随机值:0xcccccc
*p = 10;//非法访问内存了,这里的p就是野指针
return 0;
}
1.3.1.2 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0;i <= 11;i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*p = i;
p++;
}
return 0;
}
1.3.1.3 指针指向的空间释放
#include <stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
return 0;
}
1.3.2 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
#include <stdio.h>
int main()
{
int* p = NULL;//不知道初始化什么值,就初始化NULL
if (p != NULL)//可以这样初始化
{
*p = 20;
}
return 0;
}
1.4、 指针运算
1.4.1 指针±整数
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0;i < sz;i++)
{
//指针+-整数;指针的关系运算
*p = 1;
p++;
}
return 0;
}
1.4.2 指针-指针
例一 :
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);
//指针减指针的绝对值得到的是指针之间元素的个数
//指向同一个空间的自己真才有意义!!!!!!
return 0;
}
例二 :
#include <stdio.h>
int my_strlen(char* str)
{
char* start = str;
while (*str !='\0')
{
str++;
}
return (str - start);
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
1.4.3 指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
- 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
1.5、 指针和数组
我们看一个例子:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了)
那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //p存放的是数组首元素的地址
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:
#include <stdio.h>
//数组 :一组相同类型元素的集合
//指针变量 : 是一个变量,存放的是地址
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
//arr 是首元素地址
//&arr[0]
int* p = arr;
//数组可以通过指针来访问
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", *(p + i));
}
return 0;
}
//1 & arr[0] = 0117FCDC ? <=== = > p + 0 = 0117FCDC
//2 & arr[1] = 0117FCE0 ? <=== = > p + 1 = 0117FCE0
//3 & arr[2] = 0117FCE4 ? <=== = > p + 2 = 0117FCE4
//4 & arr[3] = 0117FCE8 ? <=== = > p + 3 = 0117FCE8
//5 & arr[4] = 0117FCEC ? <=== = > p + 4 = 0117FCEC
//6 & arr[5] = 0117FCF0 ? <=== = > p + 5 = 0117FCF0
//7 & arr[6] = 0117FCF4 ? <=== = > p + 6 = 0117FCF4
//8 & arr[7] = 0117FCF8 ? <=== = > p + 7 = 0117FCF8
//9 & arr[8] = 0117FCFC ? <=== = > p + 8 = 0117FCFC
//0 & arr[9] = 0117FD00 ? <=== = > p + 9 = 0117FD00
//所以 p+i 其实计算的是数组 arr 下标为i的地址。
//那我们就可以直接通过指针来访问数组。
1.6、二级指针
二级指针变量是用来存放一级指针变量的地址的
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;//pa是一个指针变量,以及指针变量
int** ppa = &pa;//ppa是二级指针变量
**ppa = 20;
printf("%d\n", a); //20
return 0;
}
1.7、 指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
例 :
#include <stdio.h>
//指针数组
//存放指针的数组就是指针数组
int main()
{
int a = 10;
int b = 20;
int c = 30;
int arr[10];
int* pa = &a;
int* pb = &b;
int* pc = &c;
//parr就是存放指针的数组
//指针数组
int* parr[10] = { &a,&b,&c };
//打印
int i = 0;
for (i = 0;i < 3;i++)
{
printf("%d ", *(parr[i])); //10 20 30
}
return 0;
}
二、结构体
- 结构体类型的声明
- 结构体初始化
- 结构体成员访问
- 结构体传参
2.1、结构体类型的声明
2.1.1 结构体的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
2.1.2 结构体的声明和创建
#include <stdio.h>
//人
struct Peo
{
char name[20];//名字
char tele[12];//电话
char sex[5];//性别
int high;//身高
}; //分号不可以丢
int mian()
{
struct Peo p1 = { 0 }; //结构体变量的创建
return 0;
}
2.1.3、结构成员的类型
结构的成员可以是标量、数组、指针,甚至是其他结构体。
2.1.4、结构体变量的定义和初始化
有了结构体类型,那如何定义变量,其实很简单。
#include <stdio.h>
//人
struct Peo //类型声明
{
char name[20]; //名字
char tele[12]; //电话
char sex[5]; //性别
int high; //身高
};
struct St
{
struct Peo p;
int num;
float f;
};
int main()
{
struct Peo p1 = { "张三","15555646494","男",181 }; //结构体变量的创建,初始化
struct St s = { { "李四","151941654","女",169 }, 100, 3.14f };//结构体嵌套初始化
return 0;
}
2.2、结构体成员的访问
结构体变量访问成员
- 结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。
由上面我们学会了如何声明和初始化,但是我们需要使用的时候应该怎么做,如何访问结构体成员呢。
#include <stdio.h>
//人
struct Peo //类型声明
{
char name[20]; //名字
char tele[12]; //电话
char sex[5]; //性别
int high; //身高
};
struct St
{
struct Peo p;
int num;
float f;
};
int main()
{
struct Peo p1 = { "张三","15555646494","男",181 }; //结构体变量的创建,初始化
struct St s = { { "李四","151941654","女",169 }, 100, 3.14f };//结构体嵌套初始化
printf("%s %s %s %d\n", p1.name, p1.tele, p1.sex, p1.high);//使用 . 操作符访问成员变量
printf("%s %s %s %d %d %f\n", s.p.name, s.p.tele, s.p.sex, s.p.high, s.num, s.f);
return 0;
}
结构体指针访问指向变量的成员 有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。 那该如何访问成员???
#include <stdio.h>
//人
struct Peo
{
char name[20];
char tele[12];
char sex[5];
int high;
};
struct St
{
struct Peo p;
int num;
float f;
};
void print(struct Peo* sp1)
{
pritnf("%s %s %s %d\n", (*sp1).name, (*sp1).tele, (*sp1).sex, (*sp1).high);//使用结构体指针访问指向对象的成员
printf("%s %s %s %d\n", sp1->name, sp1->tele, sp1->sex, sp1->high);
}
int main()
{
struct Peo p1 = { "张三","15555646494","男",181 }; //结构体变量的创建,初始化
struct St s = { { "李四","151941654","女",169 }, 100, 3.14f };
print(&p1);//结构体地址传参
return 0;
}
2.3、结构体传参
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
- 上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。- 原因:
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结论:
结构体传参的时候,要传结构体的地址