-
2021-09-26 10:01:02
文章目录
空指针
标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。
对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未指向任何东西,因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NULL指针。
问题思考:
如果对一个NULL指针间接访问会发生什么呢?
结果因编译器而异。
像VS就报错,像DEV这种牛逼的编译器就没事。案例探索:不允许向NULL和非法地址拷贝内存
不允许向NULL和非法地址拷贝内存:
#include<iostream> #include<math.h> #include<string.h> using namespace std; void test() { char *p = NULL; //给p指向的内存区域拷贝内容 strcpy(p, "1111"); //err char *q = (char *)0x1122; //给q指向的内存区域拷贝内容 strcpy(q, "2222"); //err } int main() { test(); return 0; }
运行上面的案例就会报错。
野指针
在使用指针时,要避免野指针的出现:
野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。什么情况下会导致野指针?
指针变量未初始化
任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
比如下面代码
void test(){ int* p = 0x001; //未初始化 printf("%p\n",p); *p = 100; }
虽然手动给出了地址,但是仍没有指向合法的内存
指针释放后未置空
有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
指针操作超越变量作用域
不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
如何规避野指针
操作野指针是非常危险的操作,应该规避野指针的出现:
初始化时置 NULL
指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。
比如上面的案例,就可以改成:
#include<iostream> #include<math.h> #include<string.h> using namespace std; void test() { int* p = NULL; //初始化 printf("%p\n", p); p = new int; *p = 100; printf("%p\n", p); printf("%d\n", *p); } int main() { test(); return 0; }
释放时 置 NULL
当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。
例如上面使用完了之后,可以进行下面的操作 释放p 置 NULL。
delete p; p = NULL;
更多相关内容 -
深入理解C语言指针
2019-09-28 08:36:51一、指针的概念 要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。 1.1、变量和地址 先写一段...一、指针的概念
要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。
1.1、变量和地址
先写一段简单的代码:
void main(){ int x = 10, int y = 20; }
这段代码非常简单,就是两个变量的声明,分别赋值了 10、20。我们把内存当做一个酒店,而每个房间就是一块内存。那么“int x = 10;”和“int y = 20;”的实际含义如下:
- 去酒店订了两个房间,门牌号暂时用 px、py 表示
- 让 10 住进 px,让 20 住进 py
- 其中门牌号就是 px、py 就是变量的地址
- x 和 y 在这里可以理解为具体的房间,房间 x 的门牌号(地址)是 px,房间 y 的门牌号(地址)是 py。而 10 和 20,通过 px、py 两个门牌,找到房间,住进 x、y。
1.2、指针变量和指针的类型
指针变量就是一个变量,它存储的内容是一个指针。如果用前面的例子,可以理解为指针变量就是一张房卡,房卡存储了房间号的信息。
在我们定义一个变量的时候,要确定它的类型。int x、char ch、float、、、在定义指针变量时也是一样的,必须确定指针类型。int 变量的指针需要用 int 类型的指针存储,float 变量的指针需要用 float 类型的指针存储。就像你只能用酒店 A 的房卡存储酒店 A 中房间号的信息一样。
二、变量的指针与指针变量
变量的指针就是变量的存储地址,指针变量就是存储指针的变量。
2.1、指针变量的定义及使用
(1)指针变量的定义
指针变量的定义形式如:数据类型 *指针名;例如:
//分别定义了 int、float、char 类型的指针变量 int *x; float *f; char *ch;
如上面的定义,指针变量名为 x、f、ch。并不是*x、*f、*ch
(2)指针变量的使用
- 取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。
- 指针运算符*(间接寻址符):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。
代码示例:
//声明了一个普通变量 a int a; //声明一个指针变量,指向变量 a 的地址 int *pa; //通过取地址符&,获取 a 的地址,赋值给指针变量 pa = &a; //通过间接寻址符,获取指针指向的内容 printf("%d", *pa);
(3)“&”和“*”的结合方向
“&”和“*”都是右结合的。假设有变量 x = 10,则*&x 的含义是,先获取变量 x 的地址,再获取地址中的内容。因为“&”和“*”互为逆运算,所以 x = *&x。
接下来做个小练习,输入 x、y 两个整数,然后将其中的值大的赋值给 x,小的赋值给 y。即:假设输入 x = 8,y = 9。就将 9 赋值给 x,8 赋值给 y。
void main(){ //声明两个普通变量 int x, y; //声明两个指针变量 int *px, *py; //声明一个临时变量,用于交换 int t; //输入两个值,赋值给 x、y scanf("%d", &x); scanf("%d", &y); //给指针变量 px、py 赋初值(关联变量 x、y) px = &x; py = &y; //利用指针来对比 x、y 的值,如果 x 的值比 y 的值小,就交换 if(*px < *py){ //交换步骤,其中*px == x、*py == y t = *px; *px = *py; *py = t; } printf("x = %d, y = %d", *px, *py); }
输入:23 45 输出结果为:x = 45, y = 23
2.2、指针变量的初始化
指针变量与其它变量一样,在定义时可以赋值,即初始化。也可以赋值“NULL”或“0”,如果赋值“0”,此时的“0”含义并不是数字“0”,而是 NULL 的字符码值。
//利用取地址获取 x 的地址,在指针变量 px 定义时,赋值给 px int x; int *px = &x; //定义指针变量,分别赋值“NULL”和“0” int *p1= NULL, *p2 = 0;
2.3、指针运算
(1)赋值运算
指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址
int *px, *py, *pz, x = 10; //赋予某个变量的地址 px = &x; //相互赋值 py = px; //赋值具体的地址 pz = 4000;
(2)指针与整数的加减运算
- 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
- 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
//定义三个变量,假设它们地址为连续的,分别为 4000、4004、4008 int x, y, z; //定义一个指针,指向 x int *px = &x; //利用指针变量 px 加减整数,分别输出 x、y、z printf("x = %d", *px); //因为 px 指向 x,所以*px = x //px + 1,表示,向前移动一个单元(从 4000 到 4004) //这里要先(px + 1),再*(px + 1)获取内容,因为单目运算符“*”优先级高于双目运算符“+” printf("y = %d", *(px + 1)); printf("z = %d", *(px + 2));
(3)关系运算
假设有指针变量 px、py。
- px > py 表示 px 指向的存储地址是否大于 py 指向的地址
- px == py 表示 px 和 py 是否指向同一个存储单元
- px == 0 和 px != 0 表示 px 是否为空指针
//定义一个数组,数组中相邻元素地址间隔一个单元 int num[2] = {1, 3}; //将数组中第一个元素地址和第二个元素的地址赋值给 px、py int *px = &num[0], *py = &num[1]; int *pz = &num[0]; int *pn; //则 py > px if(py > px){ printf("py 指向的存储地址大于 px 所指向的存储地址"); } //pz 和 px 都指向 num[0] if(pz == px){ printf("px 和 pz 指向同一个地址"); } //pn 没有初始化 if(pn == NULL || pn == 0){ printf("pn 是一个空指针"); }
三、指针与数组
之前我们可以通过下标访问数组元素,学习了指针之后,我们可以通过指针访问数组的元素。在数组中,数组名即为该数组的首地址,结合上面指针和整数的加减,我们就可以实现指针访问数组元素。
3.1、指向数组的指针
如以下语句:
int nums[10], *p;
上面语句定义了一个数组 nums,在定义时分配了 10 个连续的int 内存空间。而一个数组的首地址即为数组名nums,或者第一个元素的首地址也是数组的首地址。那么有两种方式让指针变量 p 指向数组 nums:
//数组名即为数组的首地址 p = nums; //数组第一个元素的地址也是数组的首地址 p = &nums[0];
上面两句是等价的。
如下几个操作,用指针操作数组:- *p = 1,此操作为赋值操作,即将指针指向的存储空间赋值为 1。此时 p 指向数组 nums 的第一个元素,则此操作将 nums 第一个元素赋值为 0,即 nums[0] = 1。
- p + 1,此操作为指针加整数操作,即向前移动一个单元。此时 p + 1 指向 nums[0]的下一个元素,即 nums[1]。通过p + 整数可以移动到想要操作的元素(此整数可以为负数)。
- 如上面,p(p + 0)指向 nums[0]、p + 1 指向 nums[1]、、、类推可得,p+i 指向 nums[i],由此可以准确操作指定位置的元素。
- 在 p + 整数的操作要考虑边界的问题,如一个数组长度为 2,p+3 的意义对于数组操作来说没有意义。
下面写一段代码,用指针访问数组的元素:
//定义一个整形数组,并初始化 int nums[5] = {4, 5, 3, 2, 7}; //定义一个指针变量 p,将数组 nums 的首地址赋值给 p,也可以用p = &nums[0]赋值 int *p = nums, i; //i 作为循环变量 //p 指向数组第一个元素(数组首地址),我们可以直接用间接寻址符,获取第一个元素的内容 printf("nums[0] = %d\n", *p); //输出结果为 nums[0] = 4 //我们可以通过“p + 整数”来移动指针,要先移动地址,所以 p + 1 要扩起来 printf("nums[1] = %d\n", *(p + 1)); //输出结果为 nums[1] = 5 //由上面推导出*(p + i) = nums[i],所以我们可以通过 for 循环变量元素 for(i = 0; i < 5; i++){ printf("nums[%d] = %d", i, *(p + i)); }
注:数组名不等价于指针变量,指针变量可以进行 p++和&操作,而这些操作对于数组名是非法的。数组名在编译时是确定的,在程序运行期间算一个常量。
3.2、字符指针与字符数组
在 C 语言中本身没有提供字符串数据类型,但是可以通过字符数组和字符指针的方式存储字符串。
(1)字符数组方式
这个在前面应该学习过,这里就不赘述了。
char word[] = "zack"; printf("%s", word);
(2)字符指针方式
指针方式操作字符串和数组操作字符串类似,可以把定义的指针看做是字符数组的数组名。在内存中存储大致如下,这里为了方便换了个字符串:
//除了定义一个字符数组外,还可以直接定义一个字符指针存储字符串 char *sentence = "Do not go gentle into that good night!"; //此时可以做字符串的操作 //输出 printf("%s", sentence); //通过下标取字符 printf("%c", sentence[0]); //获取字符串长度,其中 strlen 是 string.h 库中的方法 printf("%d", strlen(sentence));
注:字符指针方式区别于字符数组方式,字符数组不能通过数组名自增操作,但是字符指针是指针,可以自增操作。自增自减少会实现什么效果大家可以自己尝试运行一下
下面做个小练习,利用字符指针将字符数组 sentence 中的内容复制到字符数组 word 中:
//定义字符数组 sentence 和 word,给 sentence 赋初值 char sentence[] = "Do not go gentle into that good night!", word[100]; //定义字符指针,指向 word char *ch = word; int i; //循环赋值 for(i = 0; sentence[i] != '\0'; i++){ *(ch + i) = sentence[i]; } //在当 i 等于 sentence 的长度(sentence 的长度不包含'\0')时, //i 继续自增,此时判断 sentence[0] != '\0'不符合,跳出循环,则 i 比 sentence 长度大 1 *(ch + i) = '\0'; //输出字符串,因为 ch 指向 word,所以输出结果是一样的 printf("ch = %s, word = %s", ch, word);
注:指针变量必须初始化一个有效值才能使用
3.3、多级指针及指针数组
(1)多级指针
指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。我们先看看二级指针,它们关系如下:
其中 p 为一级指针,pp 为二级指针。二级指针定义形式如下:数据类型 **二级指针名;
和指针变量的定义类似,由于*是右结合的,所以*pp 相当于*(*p)。在本次定义中,二级指针的变量名为 pp,而不是**p。多级指针的定义就是定义时使用多个“*”号。下面用一个小程序给大家举例:
//定义普通变量和指针变量 int *pi, i = 10; //定义二级指针变量 int **ppi; //给指针变量赋初值 pi = &i; //给二级指针变量赋初值 ppi = π //我们可以直接用二级指针做普通指针的操作 //获取 i 的内容 printf("i = %d", **ppi); //获取 i 的地址 printf("i 的地址为%d", *ppi);
注:在初始化二级指针 ppi 时,不能直接 ppi = &&i,因为&i 获取的是一个具体的数值,而具体数字是没有指针的。
(2)指针数组
指针变量和普通变量一样,也能组成数组,指针数组的具体定义如下:
数据类型 *数组名[指针数组长度];
下面举一个简单的例子熟悉指针数组:
//定义一个数组 int nums[5] = {2, 3, 4, 5, 2}, i; //定义一个指针数组 int *p[5]; //定义一个二级指针 int **pp; //循环给指针数组赋值 for(i = 0; i < 5; i++){ p[i] = &nums[i]; } //将指针数组的首地址赋值给 pp,数组 p 的数组名作为 p 的首地址,也作为 p 中第一个元素的地址。 //数组存放的内容为普通变量,则数组名为变量的指针;数组存放的内容为指针,则数组名为指针的指针。 pp = p; //利用二级指针 pp 输出数组元素 for(i = 0; i < 5; i++){ //pp == &p[0] == &&nums[0],nums[0] == *p[0] == **pp printf("%d", **pp); //指针变量+整数的操作,即移动指针至下一个单元 pp++; }
3.4、指针与多维数组
讲多维数组是个麻烦的事,因为多维数组和二维数组没有本质的区别,但是复杂度倒是高了许多。这里我主要还是用二维数组来举例,但是还是会给大家分析多维数组和指针的关系。
(1)多维数组的地址
先用一个简单的数组来举例:
int nums[2][2] = { {1, 2}, {2, 3} };
我们可以从两个维度来分析:
- 先是第一个维度,将数组当成一种数据类型 x,那么二维数组就可以当成一个元素为 x 的一维数组。
- 如上面的例子,将数组看成数据类型 x,那么 nums 就有两个元素。nums[0]和 nums[1]。
- 我们取 nums[0]分析。将 nums[0]看做一个整体,作为一个名称可以用 x1 替换。则 x1[0]就是 nums[0][0],其值为 1。
我们知道数组名即为数组首地址,上面的二维数组有两个维度。首先我们把按照上面 1 来理解,那么 nums 就是一个数组,则nums 就作为这个数组的首地址。第二个维度还是取 nums[0],我们把 nums[0]作为一个名称,其中有两个元素。我们可以尝试以下语句:
printf("%d", nums[0]);
此语句的输出结果为一个指针,在实验过后,发现就是 nums[0][0]的地址。即数组第一个元素的地址。
如果再多一个维度,我们可以把二维数组看做一种数据类型 y,而三维数组就是一个变量为 y 的一维数组。而数组的地址我们要先确定是在哪个维度,再将数组某些维度看成一个整体,作为名称,此名称就是该维度的地址(这里有些绕)。
例:
//假设已初始化,二维数组数据类型设为 x,一维数组数据类型设为 y int nums[2][2][2]; //此数组首地址为该数组名称 printf("此数组首地址为%d", nums); //此数组可以看做存储了两个 x 类型元素的一维数组,则 nums[0] = x1 的地址为 printf("第二个维度的首地址为%d", nums[0]); //而 x1 可以看做存储了两个 y 类型元素的一维数组,则 y1 = x1[0] = nums[0][0] printf("第三个维度的首地址为%d", nums[0][0]);
三维数组实际存储形式如下:
实际存储内容的为最内层维度,且为连续的。对于 a 来说,其个跨度为 4 个单元;对 a[0]来说,其跨度为 2 个单元;对 a[0][0]来说,跨度为一个单元。有上面还可以得出:a == a[0] == a[0][0] == &a[0][0][0];
上面的等式只是数值上相等,性质不同。
(2)多维数组的指针
在学习指针与数组的时候,我们可以如下表示一个数组:
int nums[5] = {2, 4, 5, 6, 7}; int *p = nums;
在前面讲指针数组时,所有指针数组元素都指向一个数字,那么我们现在可以尝试用指针数组的每个元素指向一个数组:
//定义一个二维数组 int nums[2][2] = { {1, 2}, {2, 3} }; //此时 nums[0]、和 nums[1]各为一个数组 int *p[2] = {nums[0], nums[1]}; //我们可以用指针数组 p 操作一个二维数组 //p 为数组 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0] printf("nums[0][0] = %d", **p); //指针 + 整数形式,p+1 移动到 nums 的地址,*(p +1) = nums[1],则**(p + 1) = nums[1][0] printf("nums[1][0] = %d", **(p + 1)); //先*p = nums[0],再*p + 1 = &nums[0][1],最后获取内容*(*p + 1)即为 nums[0][1] printf("nums[0][1] = %d", *(*p + 1));
这里可能不能理解为什么*p + 1 = &nums[0][1],而不是 nums[1]。*p 获得的是一个一维数组,而 int 数组 + 1 的跨度只有 4 个字节,也就是一个单元。前面 p 是一维数组的指针,其跨度为一个数组。所以*p + 1 = &nums[0][1],而 p + 1 = nums[1]。
四、指针与函数
前面学习函数学到,函数参数可以为 int、char、float 等,但是在操作时,这些参数只作为形参,所有操作都只在函数体内有效(除对指针的操作外),那么今天来学习一下指针作为函数参数。
4.1、函数参数为指针
我们直接做一个练习,定义一个函数,用来交换两个变量的内容。
void swap(int *x, int *y); void main(){ int x = 20, y = 10; swap(&x, &y); printf("x = %d, y = %d", x ,y); } void swap(int *x, int *y){ int t; t = *x; *x = *y; *y = t; }
代码非常简单,我也就不细讲了。这里传入的参数为指针,所以调用 swap 方法后 x,y 的内容发生了交换。如果直接传入 x,y,那么交换只在 swap 中有效,在 main 中并没有交换。
4.2、函数的返回值为指针
返回值为指针的函数声明如下:
数据类型 *函数名(参数列表){ 函数体 } //例如: int s; int *sum(int x, int y){ s = x + y; return &s; }
在函数调用前要声明需要对函数声明(有点编译器不需要)
int s; void mian(){ int *r = sum(10, 9); printf("10 + 9 + %d", *r); } int *sum(int x, int y){ s = x + y; return &s; }
除了上面的操作,更实用的是返回一个指向数组的指针,这样就实现了返回值为数组。
4.3、指向函数的指针
C 语言中,函数不能嵌套定义,也不能将函数作为参数传递。但是函数有个特性,即函数名为该函数的入口地址。我们可以定义一个指针指向该地址,将指针作为参数传递。
函数指针定义如下:
数据类型 (*函数指针名)();
函数指针在进行“*”操作时,可以理解为执行该函数。函数指针不同与数据指针,不能进行+整数操作。
下面举个例子,来使用函数指针:
#include <string.h> /** * 定义一个方法,传入两个字符串和一个函数指针 p,用 p 对两个字符串进行操作 */ void check(char *x, char *y, int (*p)()); void main(){ //string.h 库中的函数,使用之前需要声明该函数。字符串比较函数 int strcmp(); char x[] = "Zack"; char y[] = "Rudy"; //定义一个函数指针 int (*p)() = strcmp; check(x, y, p); } void check(char *x, char *y, int (*p)()){ if(!(*p)(x, y)){ printf("相等"); }else{ printf("不相等"); } }
利用函数指针调用方法具体操作如下:
(*p)(x, y);
指针除了这些地方,还在结构体中用处巨大。今天就先讲到这里~·
-
【C语言】指针的基本知识
2022-03-18 14:11:06C语言指针的基本知识详解目录
一、指针的概念
1.1、变量和地址
所谓指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。
总结:指针就是变量,用指针存放地址(口语中说的指针通常指的是指针变量)
1.2、指针变量和指针的类型
指针变量就是一个变量,它存储的内容是一个指针。在我们定义一个变量的时候,要确定它的类型。在定义指针变量时也是一样的,必须确定指针类型。int 变量的指针需要用 int 类型的指针存储,float 变量的指针需要用 float 类型的指针存储。
指针类型决定了:指针解引用的权限有多大与指针走一步走多大字节(步长)
整型指针+1跳过一个整型。字符指针+1跳过一个字符
二、指针变量
2.1、指针变量的定义及使用
(1)指针变量的定义
指针变量的定义形式如:数据类型 *指针名;例如:
int* x;//整型指针变量 float* f;//浮点型指针变量 char* ch;//字符型指针变量
指针的变量名分别是:x;f和ch。而int*;float*与char*分别是他们中存储的数据的类型。
(2)指针变量的使用
取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。
指针运算符*(解引用运算符 ):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。//声明了一个普通变量 a int a; //声明一个指针变量,指向变量 a 的地址 int *pa; //通过取地址符&,获取 a 的地址,赋值给指针变量 pa = &a; //通过间接寻址符,获取指针指向的内容 printf("%d", *pa);
2.2、指针运算
(1)指针与整数的加减运算
指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。
(2)指针减指针
前提是两个指针指向同一个空间。
指针减指针得到两个指针间的元素个数
例: Arr[9]指向9与10中间部分(下标)
三、野指针
3.1概念:
野指针就是指针指向的为止是不可知的(随机的,不正确的,没有明确限制的)
3.2野指针的成因
(1)指针未初始化
(2)指针越界
如图:在第十次 访问时,可以进入到循环,相当于从第10个点向后访问4个字节,之后的字节不属于原数组,则越界了,则称为野指针。(只有在超出范围后,在会发生崩溃)
//越界访问 *int arr[10] ={ 0 }; int* p = arr; int i=0; for (i=0; i <= 10; i++)//共执行了11次,而arr数组总共只有10个元素 { *p = i; p++; }
(3)指针指向的空间释放
int* test()//局部变量 { int a = 10; return &a; } int main() { int *p=test();//返回值是a的地址,而局部变量在引用之后就会销毁。 *p = 20; return 0; }
3.3如何规避野指针
1.指针初始化
2.小心指针越界。
3.指针指向空间释放即置NULL
4.指针使用之前检查有效性。
四、字符指针
4.1字符指针类型在指针的类型中我们知道有一种指针类型为字符指针 char* ;一般使用:int main() { const char* pstr = "hello ";//这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; }
本质是把字符串 hello首字符的地址放到了pstr中。4.2例题
int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; const char *str3 = "hello bit."; const char *str4 = "hello bit."; if(str1 ==str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if(str3 ==str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
最终输出的结果是
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域。当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
五、指针与数组
之前我们可以通过下标访问数组元素,学习了指针之后,我们可以通过指针访问数组的元素。在数组中,数组名即为该数组的首地址,结合上面指针和整数的加减,我们就可以实现指针访问数组元素。
5.1、指针与二维数组
(1)二维数组的地址
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int *p=a[0];//此处的p内存放的数组a第一行的地址。
5.2、多级指针
(1)多级指针(指向指针的指针)
指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。
指针变量也是一种变量,也会占用存储空间,也可以使用 &获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号 *。p1 是一级指针,指向普通类型的数据,定义时有一个 *;p2 是二级指针,指向一级指针 p1,定义时有两个*。例:假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:
六、指针数组
1.parr的第一个数组里放arr1的首元素地址
2.parr每个元素的类型是int*。
3.该数组里每个元素都是指针,所以它是一个指针数组
4.Parr[i][j],也可以写成parr[i]+j,
5.相当于地址+j为向后挨个元素指向
七、数组指针
7.1、数组指针的定义
如以下语句:
int (*p)[10]; //解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。 //所以p是一个指针,指向一个数组,叫数组指针。 //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
7.2、&数组名与数组名
让我们看一段代码
#include <stdio.h> int main() { int arr[10] = { 0 }; printf("arr = %p\n", arr); printf("&arr= %p\n", &arr); printf("arr+1 = %p\n", arr+1); printf("&arr+1= %p\n", &arr+1); return 0; }
运行结果如下
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型。
数组的地址+1,跳过整个数组的大小。7.3、数组指针的使用
#include <stdio.h> void print_arr1(int arr[3][5], int row, int col)//形参用数组来接受 { int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); }}} void print_arr2(int (*arr)[5], int row, int col)//形参用数组指针来接受 { int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) {printf("%d ", arr[i][j]);} printf("\n");}} int main() { int arr[3][5] = {1,2,3,4,5,6,7,8,9,10}; print_arr1(arr, 3, 5); //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 print_arr2(arr, 3, 5); return 0; }
八、指针与函数
8.1、函数指针的定义
returnType (*pointerName)(param list);
其中,returnType 为函数返回值类型,pointerNmae 为指针名称,param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。
注意( )的优先级高于*,第一个括号不能省略,如果写作returnType *pointerName(param list);就成了函数原型,它表明函数的返回值类型为returnType *。8.2、指向函数的指针int (*pf[4])(int,int)={Add,Sub,Mul,Div};
函数指针数组,在函数指针的基础上,将指针变为指针数组
总结
以上就是初阶指针的基本内容了!!!!非常感谢你能看到这里!
如果你觉得你有些想法和我一样,想和我一起提升自己可以关注私信我,与我一起进步,一起共同努力!!!!!
-
c语言中指针有什么用?
2021-05-19 19:28:41一、什么是指针C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中...一、什么是指针
C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量。
前面已经提到内存其实就是一组有序字节组成的数组,数组中,每个字节大大小固定,都是 8bit。对这些连续的字节从 0 开始进行编号,每个字节都有唯一的一个编号,这个编号就是内存地址。示意如下图:
这是一个 4GB 的内存,可以存放 2^32 个字节的数据。左侧的连续的十六进制编号就是内存地址,每个内存地址对应一个字节的内存空间。而指针变量保存的就是这个编号,也即内存地址。
二、为什么要使用指针?
在C语言中,指针的使用非常广泛,因为使用指针往往可以生成更高效、更紧凑的代码。总的来说,使用指针有如下好处:
1)指针的使用使得不同区域的代码可以轻易的共享内存数据,这样可以使程序更为快速高效;
2)C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等;
3)C语言是传值调用,而有些操作传值调用是无法完成的,如通过被调函数修改调用函数的对象,但是这种操作可以由指针来完成,而且并不违背传值调用。
三、如何声明一个指针
3.1 声明并初始化一个指针
指针其实就是一个变量,指针的声明方式与一般的变量声明方式没太大区别:int *p; // 声明一个 int 类型的指针 p
char *p // 声明一个 char 类型的指针 p
int *arr[10] // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
int (*arr)[10] // 声明一个数组指针,该指针指向一个 int 类型的一维数组
int **p; // 声明一个指针 p ,该指针指向一个 int 类型的指针
指针的声明比普通变量的声明多了一个一元运算符 “*”。运算符 “*” 是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象。在上述的声明中: p 是一个指针,保存着一个地址,该地址指向内存中的一个变量; *p 则会访问这个地址所指向的变量。
声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前,指针必须进行初始化:或是使他指向现有的内存,或者给他动态分配内存,否则我们并不知道指针指向哪儿,这将是一个很严重的问题,稍后会讨论这个问题。初始化操作如下:/* 方法1:使指针指向现有的内存 */
int x = 1;
int *p = &x; // 指针 p 被初始化,指向变量 x ,其中取地址符 & 用于产生操作数内存地址
/* 方法2:动态分配内存给指针 */
int *p;
p = (int *)malloc(sizeof(int) * 10); // malloc 函数用于动态分配内存
free(p); // free 函数用于释放一块已经分配的内存,常与 malloc 函数一起使用,要使用这两个函数需要头文件 stdlib.h
指针的初始化实际上就是给指针一个合法的地址,让程序能够清楚地知道指针指向哪儿。
3.2 未初始化和非法的指针
如果一个指针没有被初始化,那么程序就不知道它指向哪里。它可能指向一个非法地址,这时,程序会报错,在 Linux 上,错误类型是 Segmentation fault(core dumped),提醒我们段违例或内存错误。它也可能指向一个合法地址,实际上,这种情况更严重,你的程序或许能正常运行,但是这个没有被初始化的指针所指向的那个位置的值将会被修改,而你并无意去修改它。用一个例子简单的演示一下:#include "stdio.h"
int main(){
int *p;
*p = 1;
printf("%d\n",*p);
return 0;
}
这个程序可以编译通过,但是运行的话会报错,报错信息如下:
要想使这个程序运行起来,需要先对指针 p 进行初始化:#include "stdio.h"
int main(){
int x = 1;
int *p = &x;
printf("%d\n",*p);
*p = 2;
printf("%d\n",*p);
return 0;
}
这段代码的输出结果如下:
可以看到,对指针进行初始化后,便可以正常对指针进行赋值了。
3.3 NULL指针
NULL 指针是一个特殊的指针变量,表示不指向任何东西。可以通过给一个指针赋一个零值来生成一个 NULL 指针。#include "stdio.h"
int main(){
int *p = NULL;
printf("p的地址为%d\n",p);
return 0;
}
/***************
* 程序输出:
* p的地址为0
***************/
可以看到指针指向内存地址0。在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是为操作系统保留的。但是,内存地址 0 有一个特别重要的意义,它表明改指针不指向一个可访问的内存位置。
更多web开发知识,请查阅 HTML中文网 !!
-
C语言 指针的定义
2019-03-04 16:38:271、指针变量的定义 int a =10;该语句表示定义一个整型变量值为10,a保存的数据为10,为了方便访问变量a,需要a的具体地址。 int *p=&a;该语句表示定义一个变量p,该变量为整型指针变量,用于保存整型变量... -
C语言通过指针引用数组
2021-05-21 05:28:25C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。引入指针变量后,就可以用两种方法来访问数组元素了。如果p的初值为&a[0],则:p+i和a+i就是a[i]的地址,或者说它们指向a... -
c语言技术this指针
2014-03-25 21:40:27c语言指针的用法详细解析,是一片很好的文档,很值得一看,吐血推荐 -
C语言——指针移位4
2022-04-28 17:28:09指针的移位跟指向对象的数据类型有关 //例 int a = 10; //定义一个int类型的变量a,并且赋值10; int *p; //定义一个指针变量p,可以指向int类型的变量 char *q; //定义一个指针变量q,可以指向char... -
C语言空指针
2020-04-06 13:10:12分析:空指针保证与任何对象或函数的指针值都不相等,也就是说空指针不会指向任何对象或函数的地址; 典型用法:malloc函数,当分配内存失败时就会返回空指针,因而空指针绝对不等同于未初始化的指针。 NULL是一个宏... -
C语言指针详解(经典,非常详细)
2019-06-01 17:26:12要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,... -
C语言中的指针详解
2021-11-01 14:12:21C语言中指针是一种数据类型,指针是存放数据的内存单元地址。 计算机系统的内存拥有大量的存储单元,每个存储单元的大小为1字节,为了便于管理,必须为每个存储单元编号,该编号就是存储单元的“地址”,每个存储... -
C语言中指针使用技巧.doc
2021-05-21 04:04:03C语言中指针使用技巧C语言中指针使用技巧摘 要 指针使得C语言在编程的过程当中增加了灵活性,它让程序员可以直接和地址打交道,而不仅仅使用系统栈分配的地址。掌握指针的应用,可以使程序简洁、紧凑、高效。笔者在从事... -
c语言指针打印
2020-09-25 15:25:03关于指针打印的一维数组运用 int main(){ //指针与一维数组 //数组名就是数值的起始地址,也是第一个元素的地址,数组名就是一个常量指针 int array[]={1,2,3,4,5}; int*ptr_array=array; // ptr_array=ptr_... -
C语言空指针NULL详解
2019-11-04 13:04:25在C语言中,如果一个指针不指向任何数据,我们就称之为空指针,用NULL表示。例如: int *p = NULL; NULL 是一个宏定义,在stdio.h被定义为: #define NULL ((void *)0) 那么我们来看看什么是空指针? [6.3.... -
C语言指针的定义及基本使用
2020-05-11 22:13:32指针是C语言中一个非常重要的概念,也是C语言的特色之一。使用指针可以对复杂数据进行处理,能对计算机的内存分配进行控制,在函数调用中使用指针还可以返回多个值。 地址和指针是计算机中的两个重要概念,在程序... -
C语言中指针与取地址符&详解
2019-10-31 22:19:27关于指针与取地址符 1.什么是指针? (1)首先,我们要明白什么叫做指针变量名,对于 int *p,这里的指针变量名不是 p 而是 p ,类似与 Java 中定义数组是 int [] array ,所以我习惯将 p 叫做 int * 类型 (2)通俗的... -
理解C语言指针
2019-03-25 14:40:45本人在初学的时候认为c语言中指针很好理解,但身边好多同学一直在说老师讲的指针太抽象了,看不到,摸不着,非常难理解,甚至学了4年计算机,毕业了,不少同学还说不清楚指针是什么,遇到指针的问题必定出错,这里... -
c语言指针之二级指针示例
2021-01-20 06:37:48就是地址,指针就是两个用途提供目标的读取或改写,那么二级指针就是为了提供对于内存地址的读取或改写指针的表现形式是地址,核心是指向关系指针运算符“*”的作用是按照指向关系访问所指向的对象.如果存在A指向B... -
C语言-指针操作
2016-11-30 23:29:160.引入 在C语言中, -
C语言指针的学习心得
2021-12-20 21:08:21在计算机科学中,指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思... -
C语言中指针的作用
2019-12-22 12:54:10指针是C语言的一个重要特性。他提供了引用数据结构(包括数组)的元素的机制。与变量类似,指针也有两个方面:值和类型。他的值表示某个对象的位置,而他的类型表示那个位置上所存储对象的类型(比如整数或者浮点数... -
C语言指针用法和举例
2020-08-20 09:55:29一,指针定义: 指针变量的取值范围取值0~4G,是一种数据类型(无符号整数,代表了内存编号)。...因为C语言采用的是值传递(内存拷贝),会随着变量字节数的增加而降低运行效率而传递变量的地址永 -
c语言课件指针.pptx
2020-02-22 18:38:39预 备 知 识;地址为了对内存中的某个存储单元进行访问要为...指针的对象当把变量的地址存入指针变量后我们就可以说这个指针指向了该变量 ;10.2 变量的指针和指向变量的指针变量;定义中的*表示所定义的变量是指针变量但 -
C语言中指针的使用方法
2018-08-17 16:07:23使用指针时,必须将它指向一个变量的地址或者为它分配空间方能使用,如下所示: #include<stdio.h> #include <stdlib.h> int main(int argc, char const *argv[]) { int a[5]={0,1,... -
【C语言】指针变量的定义、使用及初始化
2022-03-22 22:28:53,取操作对象的地址 2)指针运算符*,操作对像的地址,获取存储的内容,与取地址运算符为逆运算 注意:取地址运算符与指针运算符都是右结合的,*&x的操作是先取x的地址,然后再取该地址的值,即*(&x),相 -
C语言中的指针有什么作用
2021-08-21 00:08:59C语言中的指针的作用是:通过指针不仅可以对数据本身,还可以对存储数据的变量地址进行操作。指针就是内存地址,指针变量是用来存放内存地址的变量。指针定义:指针,是C语言中的一个重要概念及其特点... -
C语言指针的内容
2021-12-18 14:19:18但在指针的内容里面,还有一个知识点叫做:指针指向对象的类型。比如,在int *a中,指针指向对象的指针类型是int。它描述了指针指向的那个对象的类型。那么我举一个比较难的例子。int (*a)[10]中,变量名是什么?... -
C语言指针引用数组
2020-10-16 21:32:54前言:C语言中指针玩的是什么,是内存,要想学好指针的小伙伴们要先对数据在内存中是怎么玩的做一番了解~ 当在程序中定义一个变量时,系统会根据其数据类型为其开辟内存空间,... -
C语言指针知识点小结
2020-01-10 15:40:06C语言指针基础知识点(一)–指针及指针变量 C语言指针基础知识点(二)–指针变量的引用 C语言指针基础知识点(三)–指针变量作为函数参数 C语言指针基础知识点(四)–通过指针引用数组 C语言指针... -
C语言指针及占据内存空间
2021-05-21 16:40:31先了解内存地址,才更好的理解指针!我们可以把内存想象为成一列很长很长的 货运火车 ,有很多大小相同的车厢,而每个车厢正好相当于在内存中表示 一个字节 。这些车厢装着不同的货物,就像我们的内存要存着各式各样...