【C++知识】关于C++的一些基础知识

前言

         这一章介绍了C++的一些基础知识,包括引用、指针和const限定符等,可能不是很全,主要是一些需要注意的小细节,关于更详细的知识,大家可以自行去看书籍。

最后,如果有理解不对的地方,希望大家不吝赐教,谢谢!

【C++系列】【下一章:字符串、向量和数组

一、引用

      一般在初始化对象时,初始值会被拷贝到新建的对象中,然而定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起,因此无法令引用重新绑定到另一个对象,因此引用必须初始化,之后每次使用这个引用都是访问它最初绑定的那个对象。

      引用并非对象,它只是为一个已经存在的对象所起的另一个名字。引用不是对象,所以不能定义引用的引用。

对象:通常情况下,对象是指一块能存储数据并具有某种类型的内存空间

有些人把对象和值区分开来,其中对象指能被程序修改的数据,而值指只读的数据。

我们在使用对象这个词时,并不严格区分是类还是内置类型,也不区分是否命名或是否只读。

对象允许赋值和拷贝。

敲重点:(1)引用并非对象

              (2)引用和初始化值的关系是:绑定,即别名,当其中一个的值改变时,另一个跟着改变

              (3)引用必须初始化

              (4)必须用对象来初始化引用 

              (5)引用类型必须和初始化对象类型一样

引用常用在函数的参数地方,避免了实参给形参赋值

 声明符写成&d的形式来定义引用类型。其中d是声明的变量名

举例:

#include<iostream>

using namespace std;

int main()
{
	int i, &in = i;
	i = 5;
	in = 10;
	cout << "1=" << i << " in=" << in << endl;
	return 0;
}

 结果:

                                                                                              图1 

二、指针

      指针是指向另一种类型的复合类型,与引用类似,指针也实现了对其他对象的间接访问。与引用不同的是:(1)指针本身就是一个对象,允许对指针赋值和拷贝,指针可以先后指向几个不同的对象;(2)指针无须在定义时赋初始值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

敲重点:(1)指针是对象

              (2)指针不需要预先初始化

              (3)指针可以改变指向的对象

              (4)指针类型和指向的对象类型须一样

              (5)不能定义指向引用的指针

指针的值(即地址)应属于下列4种状态之一:

  1. 指向一个对象
  2. 指向紧邻对象所占空间的下一个位置
  3. 空指针,意味着指针没有指向任何对象
  4. 无效指针,也就是上述情况之外的其他值

声明符写成*d的形式,其中d是变量名,每个变量前面必须有符号*。

解引用符(操作符*)来访问该对象 ,仅适用于那些确实指向了某个对象的有效指针。

int ival=42;
int *p=&ival;

 如上面代码,p存放着ival的地址,*p得到p所指的对象,即ival

关键概念:某些符号有多重含义

  • &紧随着类型名出现,因此是声明一个引用类型
  • &出现在表达式中,是一个取地址符
  • *紧随着类型名出现,因此是声明一个指针类型
  • *出现在表达式中,是一个解引用符

在不同的场景下,完全可以看成两个不同的符号看待

 空指针

     空指针不指向任何对象。在试图使用一个指针之前代码可以首先检查它是否为空。以下列出几个生成空指针的方法:

int *p1=nullptr;  //可以转换成任意其他指针类型

int *p2=0;

int *p3=NULL;  //需要首先#include<cstdlib>,预处理变量

 预处理器:运行于编译过程之前的一段程序;预处理变量:不属于命名空间std,它由预处理器负责管理,因此可以直接使用预处理变量,而不需要在前面加上std::,当用到一个预处理变量时,预处理器会自动地将它替换实际值。

 建议:初始化所有指针

关于指针的赋值:有时候要想搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指的对象的值不太容易,最好的办法就是记住赋值永远改变的是等号左侧的对象。 

void* 指针

       特殊的指针类型,可用于存放任意对象的地址。拿它和别的指针比较、作为函数的输入或输出,或者赋给另一个void* 指针。不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型。

      概括来说,以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。

 理解复合类型的声明

int i=1024,*p=&i,&r=i;

注意int *的基本数据类型是int而非int*,*仅仅是修饰了p而已,建议在定义指针类型或者引用类型时,修饰符紧跟变量名。

指向指针的指针

       指针是内存中的对象,像其他对象一样也有自己的地址,因此允许把指针的地址再存放到另一个指针当中。通过*的个数可以区分指针的级别。 

指向指针的引用

指针是对象,所以存在对指针的引用:int *p; int *&r=p;   //r是一个对指针p的引用 ,r就是p的一个别名,完全可以当作p来用

Tip:面对一个比较复杂的指针或者引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义,离变量名最近的符号对变量的类型有最直接的影响。 

三 、const限定符

       const对象一旦创建后其值就不能再改变,所以const对象必须初始化。可以运行时初始化,也可以编译时初始化。

默认状态下,const对象仅在文件内有效

       如果程序包含多个文件,则每个用了const对象的文件都必须能访问到它的初始值才行,要做到这一点,就必须在每个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定仅在文件内有效,当多个文件中出现了同名的const变量时,其实等同于文件中分别定义了独立的变量。

如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。

const的引用

       与普通引用不同的是,对常量的引用不能被用作修改它所定的对象。

       ”对const的引用“简称为”常量引用“,这只是个简称而已,引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系本身。

初始化和对const的引用

引用类型必须和所引用对象的类型一致,但有两个例外:(1)在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。可以是对象、字面值,甚至是一般表达式

对const的引用可能引用一个并非const的对象 

 指针和const

指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。第二个例外:(2)允许令一个指向常量的指针指向一个非常量对象。

Tip:所谓指向常量的指针或引用,不过是指针或引用”自以为是“罢了,它们觉得自己指向了常量,所以自觉地不去改变所指对象的值,但没有规定那个对象的值不能通过其他路径改变。 

const指针

        指针是对象,而引用不是,因此就像其他对象类型一样,允许把指针本身定义为常量,常量指针必须初始化。而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐藏着一层意味,即不变的是指针本身的值而非指向的那个值。

       指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。 

总结一下const int *和int *const:

int i=4;

const int *ptr=&i;   //(1)

int *const ptr1=&i;   //(2)

在这里,我们假设i的地址是1234,对于(1)从右至左看,首先是一个指针,指向的是一个整形常量,所以不能通过*ptr来改变i的值,但i可以通过其他的途径改变,再就是i的地址是1234,所以ptr=1234,但指针并不是一个常量指针,所以可以改变ptr的值,即可以改变它指向其他地址空间。对于(2)从右至左看,首先是一个常量,表示自身肯定不能修改,前面的*是一个指针修饰符,代表这个常量是一个指针常量,而指向的数据类型是一个int对象,对于ptr1自身的值,也就是i的地址,这是不能改变的,即ptr只能指向这个地址,但这个地址所存的数据不一定不能改变,这个需要看所存数据的类型,而*ptr1代表的就是那个所存入的数据,即可以通过*ptr1来改变所存入在这个空间的数据。

下面看下代码运行结果来说明这个问题:

#include<iostream>

using namespace std;

int main()
{
	int i = 10;
	int j = 6;

	//常量指针
	int *const ptr = &i;
	cout << "i=" << i <<"  j="<<j<<endl;
	cout << "ptr="<<ptr<<"  *ptr=" << *ptr << endl;
	*ptr = 5;   //进行改变存入这个地址空间的值
	cout << "*ptr=5之后:" << endl;
	cout << "ptr=" << ptr << "   *ptr=" << *ptr <<"  i="<<i<<endl;
	cout << "----------------" << endl;
	//ptr = &j;    //不可以改变ptr的指针方向

	//指向常量的指针
	int const* ptr1 = &i;
	cout << "ptr1=" << ptr1 << "  *ptr1=" << *ptr1 << endl;
	ptr1 = &j;  //进行改变这个指针的方向,即指向别的地址
	cout << "ptr1=&j之后:" << endl;
	cout << "ptr1=" << ptr1 << "  *ptr1=" << *ptr1 <<"  i="<<i<< endl;
	//*ptr1=6; //不可以通过解引用符来改变所存的数据值

	return 0;
}

 结果:

 顶层const

        指针本身是一个对象,它又可以指向另外一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个独立的问题。用名词顶层const表示指针本身是个常量,而名词底层const表示所指的对象是一个常量。

        底层const的限制不能忽视,当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换,一般来说,非常量可以转换成常量,反之不行。

constexpr和常量表达式

       常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。一个对象(或表达式)是不是常量表达式由它的是数据类型和初始值共同决定的。

constexpr变量

       声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。新标准允许定义一种特殊的constexpr函数,这种函数应该足够简单以使地编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量了。

       一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型。

字面值类型

        算术类型、引用和指针都属于字面值类型。自定义类、IO库、string类型则不属于字面值类型,也就不能被定义成constexpr。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。

指针和constexpr

       在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。

       constexpr int *q=nullptr;   //q是一个指向整数的常量指针,把它定义的对象为了顶层const 

指针、常量和类型别名

       pstring是类型char*的别名,声明语句中用到pstring时,其基本数据类型是指针。用char*重写了声明语句后,数据类型就变成了char。

auto类型说明符

      auto让编译器通过初始值来推算变量的类型,auto定义的变量必须有初始值。 

decltype类型指示符

       作用是选择并返回操作数的数据类型。decltype的结果与表达式形式密切相关,对于decltype所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同,如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式,变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。 

切记:decltype((variable))的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烊萌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值