C++
作者插入
这个分类的内容主要是用来记录C++语法的,当然也不可能详细地介绍,只是有感而发。当然,如果有什么好的想法要我写的话,请转到Github的Issues提交问题。
指针和数组¶
指针是什么¶
指针是一种C/C++变量,其存储的信息是变量的地址和变量的类型信息。每种类型,包括自定义类型,系统都会自动派生出对应的指针类型(在变量前加*),其定义只要在参数前加上*就可以了。
在这里,我们需要注意以下几点:
-
变量地址指该变量所占内存空间的首地址。例如
int a
占了内存105-108,则a
的地址为105. -
当该变量为自定义类型时,其所占的空间为其成员所占的空间和,不包含动态申请的空间,一个特定的类具有唯一确定的空间大小(不包括动态申请的内存空间)。例如有以下类型。则其所占的空间为12Bytes(假设一个指针占4Bytes),获取一个变量或者类型占用的内存空间可以用
sizeof()
。
1 2 3 4 5 6 |
|
&
和*
运算符默认为取地址和解指针运算符,当然也可以根据实际需要来重载。
指针的+
和-
运算符¶
1 2 3 4 5 6 7 8 9 10 11 12 |
|
读者须知
在涉及到讲解的代码块时,推荐自己先跑一遍代码,然后与本文所讲的进行对比。
int*(a) + int(b)
表示指针的地址向后移动b * sizeof(int)
位,也就是b个int
型的长度,这样做是为了方便数组和移位的操作。
当然int*(a) - int*(b)
和上面类似,表示中间相差的元素个数。
指针的下标运算¶
指针的下标运算和数组的很像,实际上下标运算通常在动态数组中应用,例如a[2]
表示动态数组a
的第二个元素。根据上文的运算法则,a[2]<==>*(a+2)
,其两者实现的效果等价。
奇怪的"类型"-数组¶
作者插入
其实本来我想先讲4再讲3,但既然提到了数组,那就讲讲数组这个奇怪的东西。
数组并不是一个真正的类型。而是一个不完全类型,静态数组的容量大小只能在编译前确定,而且它还具有很多奇怪的性质。
数组是用一块连续的内存空间来存储的,因此,C++的数组可以通过数组名来获取其首地址,用下标运算获取对应的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
你会发现,打出来的两行东西是不一样的,第一行是20
,第二行是4
。这是因为前一个sizeof()
可以通过上下文定位数组(也就获取到了它元素的个数),而函数中的sizeof()
无法获取数组的长度信息(因为已经是一个指针了),也就是说,数组只能通过指针的方式传递,且会丢失信息。
二级指针和指向数组的指针¶
提示
二级指针包括指针的指针,也包括指向数组的指针。当然,这里的二级指针的含义指指向指针的指针。
1 2 3 4 5 6 7 8 9 |
|
第一个就是二级指针,第二个就是指向数组的指针,这个很少用,真的很少用,当然,请注意int (*pa)[10]
和int *pa[10]
的区别,一个是指向数组的指针,一个是基类型为int*
的一个数组。
引用¶
引用的概念¶
C++
的指针使得我们可以直接和内存进行交互。然而,有时候我们仅仅需要一个传递一个变量,而不关注其他的内容。所以C++
设计了引用,从而减少了&
和*
的操作。
引用是一种已经包装过的常指针,其很多特性和常指针很像,然而其不需要复杂的&
和*
操作。引用可以形象化地理解为给变量取别名。当引用传递给参数时,可以理解为把整个变量给了函数。
因为引用本质上是一个常指针,因而 1)不能用一个常量来给引用赋值。 2)引用定义时就要初始化,且引用的变量在中途不能更改。
引用传递和值传递¶
无论是普通基类对象还是指针,当函数调用或者返回的时候都属于值传递的方式,当参数是类对象时,其会调用拷贝构造函数来生成一个副本,作为函数内的临时对象。而指针是通过拷贝地址的方式来间接实现传递变量的。
而引用虽然本质上是常指针,但是从形式上来说其确实传递了变量,因而当一个参数为引用时,其参数必须是可修改的左值。例如参考以下代码,其输出将为10。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
引用的作用¶
引用就其功能来说完全可以通过指针来实现,但是引用的传递参数更加地方便,也提高了程序的阅读能力。其中cin >>
便是一个很好地使用了引用的例子,因为流输入是个变量赋值的。
其调用参数和使用参数的时候和普通的对象几乎没有什么区别,所以也提高了代码的可维护性,引用在必要的地方还可以节省一次创建拷贝的过程,节省了内存空间。
const修饰符¶
const
表明了常的属性,所以要理解const
,就需要理解常这个概念。const
可以修饰变量,指针,函数和成员/字段,我们将非const
赋给一个const
对象,但是反过来就不可以。
修饰变量¶
这里的变量不包括指针,既可以是基类型,也可以是类对象。当然,当一个变量用const
来修饰时,表明其成员无法修改,对于类对象来说,其还有不能调用非const
函数的特征(因为const
函数可以保证其数据成员不修改)。
除此之外,通过#define
也可以实现类似的功能,但是其原理是通过文本替换来实现的。
修饰函数¶
当一个函数声明为const
时,其表示这个一个不会修改数据成员的函数,任何企图修改数据成员的函数都会编译不通过。这样可以确保const
对象调用的方法不会修改成员。
提示
虽然const
函数无法修改数据成员,但是如果其成员中有一个指针,则其指针指向不能变,但仍然可以通过指针来修改内存中的数据。
修饰指针¶
指针有两处地方可以修饰,一种是指针符号前,另一种是指针符号后,其区别如下
const int *p
表示指向const
数据的指针。其指针的指向可以修改,但是不能通过该指针修改其内存中的值(并没有要求其指向的是一个常量)
而int * const p
是一个常指针,其指向不能更改,但是可以通过该指针修改内存中的值。
提示
const int *p
和int const *p
属于同一种东西,是指向常量的指针变量。
const
设计出来主要用来 1)定义一个全局的变量 2)防止传递参数时函数内部修改变量的值。
浅拷贝和深拷贝¶
对于一般的类型来说,编译器默认的拷贝就是深拷贝。然而当类型包含指针类型的数据时,系统默认会拷贝指针的值,而不会去拷贝其内存中的值。这种通过拷贝后数据仍然是一份(占用同一块)内存空间的现象就叫做浅拷贝。浅拷贝可以减少变量所占的空间(因为需要拷贝的数据少了),但是会导致很多问题。
而问题主要出析构函数这一块,析构函数希望通过撤销动态空间来防止内存泄露,但是这样做会出现野指针的现象。
后续,我会通过例子来讲解浅拷贝和深拷贝的实际作用。