指针与引用:
1、指针和引用的区别(1)
区别: (1)引用只是别名,指针是一个对象,引用不能修改绑定对象,指针可以修改指向的对象 (2)有const指针,但是没有const引用,因为引用绑定的对象本身不能修改 (3)指针可以有多级,但引用只能是一级(引用不是对象,所以不能声明引用的引用) (4)指针的值可以为空,但是引用的值不能用NULL,引用在定义的时候必须初始化 (5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。 (6)"sizeof引用"得到的是所绑定的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小; (7)指针和引用的自增(++)运算意义不一样;
(8)指针间接访问时需要解引用(*),引用则不需要(引用是别名) |
2、悬空指针与野指针区别(1)
野指针(wild pointer)就是没有被初始化过的指针。
悬空指针是指针最初指向的内存已经被释放了的一种指针。 如果两个指针(p1和p2)指向同一块内存区域, 那么free(p1)后,p1和p2都成为悬空指针。如果进一步将p1设置为NULL, 那么p2还是悬空指针。诚然,使用*p1会导致非法内存访问,但是使用*p2却会出现无法预料的结果
|
如何避免使用野指针和悬空指针? |
如何避免使用野指针? 好办! 养成在定义指针后且在使用之前完成初始化的习惯就好。
避免悬空指针:智能指针的本质是使用引用计数(reference counting)来延迟对指针的释放。 |
参考文章:野(wild)指针与悬空(dangling)指针 |
int main(int argc, char *argv[])
{
int *p;
return (*p & 0x7f); /* XXX: p is a wild pointer */
}
//悬空指针
#include <stdlib.h>
int main(int argc, char *argv[])
{
int *p1 = (int *)malloc(sizeof (int));
int *p2 = p1; /* p2 and p1 are pointing to the same memory */
free(p1); /* p1 is a dangling pointer, so is p2 */
p1 = NULL; /* p1 is not a dangling pointer any more */
return (*p2 & 0x7f); /* p2 is still a dangling pointer */
}
3、C++中*和&同时使用是什么意思?(1)
类似于void *& fun (int *& a)这样的函数。
指针的引用,指针也是一个变量,可以有引用,其实就是指针变量的别名,在函数里面对a的值进行改变,那么在调用函数时传入的参数的指针值也会改变
void * fun (int *a) 如果形参是指针,传入的其实是指针形参的拷贝,函数内可以修改指针所指对象的值,但是不能改变指针的值 |
4、指针常量与常量指针(1)
常量指针 |
常量指针(被指向的对象是常量)
声明方式: const int *p; int const *p; |
指针常量 |
指针常量(指针本身是常量) 定义: 本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值因为是常量,所以不能被赋值。 关键点: 1. 它是个常量! 2. 指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化; |
5、引用作为函数参数以及返回值的好处(2)
对比值传递,引用作为函数参数以及返回值的好处 |
(1)在函数内部可以对此参数进行修改(传递实参的别名) (2)提高函数调用和运行的效率(没有了传值和生成副本的时间和空间消耗)
采用值传递,通过“形参=实参”来对形参赋值达到传值的目的,生成一个实参的副本。函数内部对形参的修改,不会对实参有任何影响。 用引用作为返回值最大的好处就是不产生被返回值的副本。 |
需要遵守的规则 |
参考链接:https://blog.csdn.net/weibo1230123/article/details/82014538 |
6、简述数组与指针的区别?(3)
7、int (*s[10])(int) 表示的是什么?(3)
8、复杂声明
void * ( * (*fp1)(int))[10];
float (*(* fp2)(int,int,int))(int);
int (* ( * fp3)())[10]();
分别表示什么意思?(3)
9、下列代码可以编译过吗?
/**************代码1*********************/
char *strA()
{
char str[]="hello world";
return str;
}
/****************代码2****************/
const char *strA()
{
char *str="hello world";
return str;
}
/***************代码3***************/
const char *strA()
{
static str[]="hello world";
return str;
}
分辨字符串常量和初始化列表快速记法: 当它初始化一个字符数组时,它就是一个初始化列表;其他任何地方,它都是一个字符串常量 char str[]="hello world"——是分配一个局部数组。 char* str="hello world"——是分配一个指针变量。
char *c="hello world"; *c='t'//这将是错误的,字符串常量存在只读区,只读区的内容无法修改。 char c[]="hello world"; c[0]='t'//这是可以的,局部区的数据是可以修改的。
代码1:由于定义的str[]是一个char数组变量,在编译期间,很明显知道了str[]是在栈上分配内存地址,所以编译器就把字符串“hello world”认为是该内存地址的内容,由于跳出函数之后被释放了
代码2:这是对代码1的修改。由于str是char型指针变量,需要将它指向一个内存地址,编译期间,由于“hello world”是一个字符串常量,需要在存放在只读区(用于存放常量和程序)。所以str指针就指向只读区,而只读取的内容是无法更改的(所以加了const)
代码3:是代码1的改进。这里static变量保证了函数退出后,变量的内存不被释放。 |
10、指针和地址的关系
|
11、区分几种定义
const char *p; //*p是const,p可变 char const * p; p内存一,*内存二,const内存二是常量,char内存二是字符常量 const (char *) p; p内存一,(char*)内存二字符变量,const,内存一常量 “(char*)”: 整个修饰p,因为不是*,不会跳转到新的内存,所以返回值还是p,还是内存1
链接:奇技淫巧 |
12、指针函数和函数指针
指针函数的本质是返回指针的函数。
函数指针的本质是指向函数的指针。
|
13、迷途指针、悬空指针
迷途指针又称为悬浮指针,当你释放(delete)内存时,并没有将指向这块内存的指针置为空指针,那么这个指针将成为迷途指针。
说到释放内存(delete),我们知道c++里面已经有malloc/free,那为什么还需要new/delete?它们之间有什么不同?
malloc/free是c++/c里面的标准库函数,new/delete是c++的运算符。它们都可以用于动态申请和释放内存,但是对于非内部数据类型的对象而言,malloc/free是无法满足动态对象的要求的。对象在创建的时候自动运行构造函数,在消亡之前自动运行析构函数。由于它们是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加给malloc/free。因此c++语言需要一个完成内存分配和初始化工作的运算符new,以及清理与释放内存的运算符delete。 malloc函数的参数是接受需要分配的内存字节数,如果内存能够满足请求量,那么将会返回指向被分配块的起始地址
|
//指针函数
#include<stdio.h>
int sum;
int *add(int a, int b)
{
sum=a+b;
return ∑
}
int main()
{
int *p;
p=add(1,2);
printf("add result is %d\n", *p);
return 0;
}
//函数指针
#include<stdio.h>
int add(int x, int y)
{
return (x+y);
}
int main()
{
int (*p)(int, int);
p=add;
printf("add result is %d\n", p(200,300));
}
复合类型 | ||
声明语句: 一条声明语句由一个基本数据类型和紧随其后的一个声明符列表组成;每个声明符命名了一个变量并指定该变量为与基本数据类型有关的某种类型; 简单声明语句:声明符是变量名,变量类型是基本数据类型;eg:int a; | ||
引用 | ||
1、作用:为对象起另外一个名字 2、int &d = a; //&d为声明符,定义引用类型,d为变量名,Int是基本数据类型 3、int & refVal; //报错:引用必须被初始化; 定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始对象一直绑定在一起。无法令引用重新绑定到另外一个对象,因此引用必须初始化。 | ||
引用即别名 | ||
1、引用并非对象,相反的,只是为一个已存在的对象起一个别名 2、因为引用并非对象,所以不能定义引用的引用 | ||
引用的定义 | ||
1、允许一条语句中定义多个引用,其中每个引用标识符必须以&开头
2、除了两种特例(初始化常量引用时允许任意表达式作为初始值(非常量对象、字面值),或者用基类引用绑定到一个派生类对象),其它所有引用的类型都要和绑定的对象严格匹配 3、引用只能绑定到对象上,不能与字面值或者某个表达式的计算结果绑定在一起
| ||
指针 | ||
指针和引用的相同与不同: 相同处:都能实现对其它对象的间接访问 不同: 1、指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以指向几个不同的对象 2、指针无须在定义时赋初值。和其它内置类型意义,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
int *d ; //*d为声明符,定义指针类型,d为变量名,Int是基本数据类型
允许一条语句中定义多个指针,其中每个指针标识符必须以*开头
不能定义指向引用的指针,因为引用不是对象,没有实际地址;
除了两种特例(const的指针,或者用基类指针指向一个派生类对象),其它所有指针的类型都要和它所指向的对象严格匹配
| ||
指针值 | ||
指针的值(即地址)应该属下列四种状态之一: 1、指向一个对象 2、指向紧邻对象所占空间的下一个位置 3、空指针,意味着指针没有指向任何对象 4、无效指针,上述情况之外的其他值。
试图拷贝或访问无效指针将引发错误,编译器不负责检查此类错误 第2种、第3种形式指针有效,但是没有指向任何对象,试图访问此类指针行为不被允许 | ||
利用指针访问对象 | ||
解引用符* 解引用操作仅适用于确实指向了某个对象的有效指针 | ||
空指针 | ||
空指针不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空
生成空指针的几个方法:
1、nullptr是c++11引入的字面值,可以转换为任意其他的指针类型 2、也可以用字面值0 3、NULL是预处理变量,头文件cstdlib中定义,值为0;当用到一个预处理变量时,预处理器自动将它替换为实际的值,因此用NULL初始化指针和用0是一样的 !!!将int变量直接赋给指针是错误的,即使变量值恰好为0
!!!建议初始化所有指针,如果使用了未经初始化的指针,则该指针所占空间的当前内容被看到一个地址值,就很难区分到底是合法还是非法了。实在不清楚指针应该指向何处,把他初始化为nullptr或者0,这样程序就能检测并知道它没有指向任何具体的对象了
| ||
指针操作 | ||
1、如果指针的值是0,if取false
2、两个指针存放的地址值相同有三种可能:
| ||
void*指针 | ||
1、void*指针可用于存放任何对象的地址,但是对该地址中到底是一个什么类型的对象并不了解 2、因此不能直接操作void*指针所指对象,可以拿它跟别的指针比较、作为函数输入输出、或者赋给另外一个void*指针
| ||
理解符合类型的声明 | ||
同一条语句中,基本数据类型只有一个,但是声明符的形式可以不同,也就是:一条定义语句可能定义不同类型的变量
指针和引用的两种写法: 1、把修饰符和变量标识符写在一起,强调变量具有符合的类型 2、修饰符和类型名写在一起,并且每条语句只定义一个变量,强调本次声明定义了一个复合类型
| ||
指向指针的指针、指向指针的引用 | ||
最简单的方法是从右向左阅读r的定义 | ||
const限定符 | ||
1、任何试图对const变量进行赋值的行为都将引发错误 2、const对象一旦创建了之后其值不再改变,所以const对象必须初始化。初始值可以是任何复杂的表达式
| ||
初始化和const | ||
const对象的主要限制是只能在const对象上执行不改变其内容的操作; 可以进行:const int 与Int的算术运算,初始化 如果利用一个对象初始化另一个对象,是不是const都无关紧要(const属性仅仅在执行改变ci的操作才发挥作用) | ||
在默认情况下,const对象仅在文件内有效 | ||
1、当多个文件同时出现了同名的const变量时,其实相当于在不同文件定义了独立的变量 2、如果想在多个文件之间共享const对象,必须在定义的变量之前添加extern关键字
| ||
const的引用 | ||
1、对常量的引用不能用来修改绑定的常量的对象 2、不能让一个非常量引用指向一个常量对象
| ||
初始化和对const的引用 | ||
1、初始化常量引用时允许任意表达式作为初始值(非常量对象、字面值),只要该表达式的结果能转换成引用的类型,如double转int即可。 程序中,ri绑定的是一个临时量对象(编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未知名对象)
当ri不是常量时,绑定的是一个临时量,还是不能通过ri改变dval的值,无意义,C++也把这种行为归为非法行为。
这就是为啥 可以用常量的引用绑定一个非常量(其实绑定的是一个临时量) 不能用非常量的引用绑定一个常量(无意义,定为非法) | ||
指针和const(与引用类似) | ||
1、指向常量的指针不能用于改变其所指的对象 2、存放常量对象的地址,只能使用指向常量的指针 3、允许另一个指向常量的指针指向一个非常量对象 (使用常量的指针仅仅要求不能通过该指针改变对象的值,并没有规定那个对象的值不能通过其他途径改变)
| ||
常量指针与指向常量的指针 | ||
1、常量指针必须初始化,而且一旦初始化完成,它的值就不再改变 |
C和指针
指针变量的内容 | |||||||||||||||||||||||||||||||||||||||||||||||||
变量的值就是该变量的内存位置所存储的数值,指针也是,只不过存储的是指向对象的地址 | |||||||||||||||||||||||||||||||||||||||||||||||||
间接访问操作符 | |||||||||||||||||||||||||||||||||||||||||||||||||
通过*单目操作符,实现间接访问或者解引用指针 | |||||||||||||||||||||||||||||||||||||||||||||||||
未初始化与非法的指针 | |||||||||||||||||||||||||||||||||||||||||||||||||
声明一个指向常量的指针,指针变量就不会被分配存储空间; 如果没有初始化,指针变量和其它变量并无区别,如果变量是静态的,被初始化为0;如果变量是自动的,根本不被初始化;
执行赋值操作, 运气好,a的初始值是一个非法地址,报错,“段违例”或“内存错误”; 严重的,指针可能包含一个合法地址,位于那个位置的值被修改
解决方法是指针必须初始化后,才能使用。 | |||||||||||||||||||||||||||||||||||||||||||||||||
指针表达式(C与指针p100) | |||||||||||||||||||||||||||||||||||||||||||||||||
++优先于*,*优先于+ 前缀++先增加再返回拷贝,后缀++先返回拷贝再增加 |
数组
数组名 | |||||||||||||||||
数组名的值是一个指针常量,也就是指针第一个元素的地址;b:指向int的常量指针 只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量
在以下两种情况中,数组名不能用指针常量表示: 1、数组名作为sizeof操作符(返回的是整个数组的长度,而不是指向数组的指针的长度) 2、&操作数(产生的是一个指向数组的指针,而不是指向常量指针的指针)
| |||||||||||||||||
下标引用 | |||||||||||||||||
除了优先级之外,下标引用和间接访问完全相同。
| |||||||||||||||||
指针与数组 | |||||||||||||||||
指针和数组并不是相等的
声明一个数组时,编译器根据声明所指定的元素数量为数组保存内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置; 声明一个指针变量时,编译器只为指针本身保留内存空间,它不为任何整型值分配内存空间。而且,指针变量并未初始化为指向任何现有内存空间,如果它是一个内存变量,甚至不会被初始化。
b++可以通过编译,a++不行,因为a的值是一个常量 | |||||||||||||||||
作为函数参数的数组名 | |||||||||||||||||
通过传值方式传递的,传递给函数的是一份指针的拷贝,可以修改它所指向的内容,但是不会修改指针实参本身
| |||||||||||||||||
声明数组参数 | |||||||||||||||||
| |||||||||||||||||
数组初始化 | |||||||||||||||||
取决于存储类型: 1、存储于静态内存的数组只初始化一次,也就是在程序执行之前 2、自动变量:自动变量位于运行时堆栈中,执行流每次进入它们所在的代码中,这类变量每次所处的内存位置可能不同,因此,自动变量在缺省情况下是未初始化的。 如果在自动变量的声明中给出了初始值,每当执行流进入自动变量声明所在的作用域时,变量就被一条隐式的赋值语句初始化。 如果每次都对数组重新初始化不值得,就要把数组声明为static
分辨字符串常量和初始化列表快速记法: 当它初始化一个字符数组时,它就是一个初始化列表;其他任何地方,它都是一个字符串常量 | |||||||||||||||||
多维数组 | |||||||||||||||||
数组元素的存储顺序:按照最右边下标率先变化的原则,行主序
下标(同间接访问)
| |||||||||||||||||
指向数组的指针 | |||||||||||||||||
| |||||||||||||||||
作为函数参数的多维数组 | |||||||||||||||||
1、与一维数组区别,一维数组形参不用知道维数,但是多维数组需要,以便确定调整因子,为函数形参的下标表达式进行求值
| |||||||||||||||||
多维数组初始化 | |||||||||||||||||
数组元素的存储顺序是根据最右边下标率先变化的原则确定的
| |||||||||||||||||
指针数组 | |||||||||||||||||
|