1、C++基础知识
1.1、指针
1、指针函数和函数指针
1)函数指针
本质是一个指针,该指针指向这个函数 int (*fun)(int x,int y);
函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分。函数指针与数据项相似,函数也有地址,也就是说在同一个函数中通过使用相同的形参在不同的时间使用产生不同的效果。一个函数名就是一个指针,它指向函数的代码,一个函数地址是该函数的进入点,也就是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用,函数指针还运行将函数作为变元传递给其他函数。
int add(int x,int y){
return x+y;
}
int sub(int x,int y){
return x-y;
}
//函数指针
int (*fun)(int x,int y);
int main()
{
//第一种写法
fun = add; cout << "(*fun)(1,2) = " << (*fun)(1,2) ; //3
//第二种写法
fun = ⊂ cout << "(*fun)(5,3) = " << (*fun)(5,3) << fun(5,3); //2 2
return 0;
}
2)指针函数
返回一个指针的函数,本质是一个函数,该函数的返回值是一个指针 int *fun(int x,int y);
typedef struct _Data{
int a;
int b;
}Data;
//指针函数
Data* f(int a,int b){
Data * data = new Data;
data->a = a;
data->b = b;
return data;
}
int main()
{
//调用指针函数
Data * myData = f(4,5);
cout << "f(4,5) = " << myData->a << myData->b; //f(4,5) = 4 5
return 0;
}
3)举个例子
-
int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
-
int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
-
int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
-
int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
2、常量指针和指针常量
-
指针常量是一个指针,读成常量的指针,指向一个只读变量,也就是后面所指明的int const 和 const int,都是一个常量,可以写作int const *p或const int *p。
-
常量指针是一个不能给改变指向的指针。指针是个常量,必须初始化,一旦初始化完成,它的值(也就是存放在指针中的地址)就不能在改变了,即不能中途改变指向,如int *const p。
3、野指针
都是是指向无效内存区域(这里的无效指的是"不安全不可控")的指针,访问行为将会导致未定义行为。
1)野指针的概念
- 野指针指向一个已删除的对象或不可用的地址的指针。
- 指针变量中的值是非法的内存地址,进而形成野指针。
- 野指针不是NULL指针,是指向不可用内存地址的指针。
2)出现原因:
- 指针定义时未被初始化;程序未对指针进行初始化,会随机指向一个区域,除了static修饰的指针;
- 指针被释放时没有置空;指针指向的内存空间在free()和delete()释放后,没有进行置空操作的话,就会称为一个野指针;
- 指针操作超越变量作用域;不要返回指向栈内存的指针或者引用,栈内存在函数结束的时候就会自动被释放;
3)解决或避免:
delete后置为NULL,新建指针时判断是否为NULL,不是则释放并置为NULL,尽量不使用超出作用范围的指针
4)野指针:delete 指针后为什么需要置为NULL?
首先delete指针只是编译器释放该指针所指向的内存空间(该空间可以给其他变量使用),而不会删除这个指针本身。这可能会导致后续申请指针时,系统新建的指针指向的地址可能会跟delete掉的指针相同,此时如果修改delete掉的指针的内容就会导致对新建的指针内容的修改。
所以为了防止这种情况的发生,需要delete掉后立即置为NULL(避免变成野指针),同时在新建指针的时候需要判断新建的指针是否为NULL,为NULL才是申请成功。
对null的delete可以无数次,因为delete会直接跳过NULL
原文链接:https://blog.csdn.net/weixin_42067304/article/details/108451031
4、悬空指针
1)悬空指针概念
悬空指针,指针最初指向的内存已经被释放了的一种指针。
int main(void) {
int * p = nullptr;
int* p2 = new int;
p = p2;
delete p2;
}
此时 p和p2就是悬空指针,指向的内存已经被释放。继续使用这两个指针,行为不可预料。需要设置为p=p2=nullptr。此时再使用,编译器会直接保错。c++引入了智能指针,C++智能指针的本质就是避免悬空指针的产生。
2)产生原因及解决办法:
指针free或delete之后没有及时置空 => 释放操作后立即置空。
5、指针加减需要注意什么?
指针加减本质是对其所指地址的移动,移动的步长跟指针的类型是有关系的,因此在涉及到指针加减运算需要十分小心,加多或者减多都会导致指针指向一块未知的内存地址,如果再进行操作就会很危险。遇到指针的计算,需要明确的是指针每移动一位,它实际跨越的内存间隔是指针类型的长度,建议都转成10进制计算,计算结果除以类型长度取得结果。
1.2、指针和引用
1、在传递函数参数时,什么时候使用指针,什么时候使用引用?
总结
- 需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的。
- 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小。
- 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式。
1)使用引用参数的主要原因有两个:
-
程序员能修改调用函数中的数据对象
-
通过传递引用而不是整个数据–对象,可以提高程序的运行速度
2)一般的原则:
-
对于使用引用的值而不做修改的函数:
-
如果数据对象很小,如内置数据类型或者小型结构,则按照值传递;
-
如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针;
-
如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间;
-
如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);
3)对于修改函数中数据的函数:
- 如果数据是内置数据类型,则使用指针
- 如果数据对象是数组,则只能使用指针
- 如果数据对象是结构,则使用引用或者指针
- 如果数据是类对象,则使用引用
2、值传递、指针传递、引用传递去区别和效率?
- 值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构体对象,将耗费一定的时间和空间。(传值)
- 指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)
- 引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)
- 效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。
3、C++中的指针参数传递和引用参数传递有什么区别?(底层)
- 指针参数传递本质上是值传递,它所传递的是一个地址值。
值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。
值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进