C++编程语言的四个基本准则,及其具体的体现
文章目录
一、最高准则:无二义性。
同一条C++语句不能同时具备两种或多种含义,每一条C++语句只能通过一种方式执行,得到唯一结果。
(1)运算符的优先级与结合性。
优先级:当一个表达式中出现了多个不同的运算符时,不同的运算符有按照等级排列的运算顺序,即运算符的优先级。从上表中可得,优先级从上到下依次递减,指针具有最高的优先级,而逗号则有最低的优先级。当符号的优先级相同时,则看运算符的结合性。
结合性:运算符的结合性是指相同优先级的运算符在同一个表达式中,且没有括号的时候,运算符和操作数的结合方式,大多数运算是从左至右计算,只有三个优先级是从右至左结合的,它们是单目运算符、条件运算符、赋值运算符。
(2)标识符不能以数字开头。
为了方便区分变量和常量,从而便于编译器编译。如2E3这样的字符既可以表示变量名,也可以表示常量:2*10^3, 同样地,0x7C7D(16进制数),015(8进制数)均具有二义性。
(3)贪心规则。
贪心规则是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。
例如在编译程序时编译器会尽可能多地结合有效的符号,而不管这样的结合是否合法。贪心规则的运行方式决定了其在某种程度上大大加快了运行的效率,但是因为其限定条件,可能经常不适用,或者报错。
(4)多重继承的二义性
1、“倒三角”问题——同名二义性
#include "iostream"
using namespace std;
class Parent_f
{
public:
void show()
{
cout<<"This is Parent_f\n";
}
};
class Parent_m
{
public:
void show()
{
cout<<"This is Parent_m\n";
}
};
class Son:public Parent_f,public Parent_m
{
public:
void display()
{
cout<<"This is son\n";
}
};
int main()
{
Son son;
son.show();
son.display();
cout << "Hello world!" << endl;
return 0;
}
上面的代码中,2个父类派生一个子类,但两个父类中都有同名的成员函数。派生出的子类产生二义性问题,编译时会报:
error: request for member 'show' is ambiguous
解决方法:
(1)利用作用域限定符(::),用来限定子类调用的是哪个父类的show()函数
son.Parent_f::show();
(2)在类中定义同名成员,覆盖掉父类中的相关成员
class Son:public Parent_f,public Parent_m
{
public:
void display()
{
cout<<"This is son\n";
}
void show()
{
cout<<"show:This is son.\n";
}
};
2、“菱形”问题——路径二义性
有最基类A,有A的派生类B、C,又有D同时继承B、C,那么若A中有成员a,那么在派生类B,C中就存在a,又D继承了B,C,那么D中便同时存在B继承A的a和C继承A的a,那么当D的实例调用a的时候就不知道该调用B的a还是C的a,就导致了二义性。
class A
{
public:
int a; // B1,B2 都将继承一个变量 a
};
class B1 : public A
{
};
class B2 : public A
{
};
class C : public B1, public B2
{
};
int main()
{
C c;
c.a = 10; // ERROR ! 二义性 !!!
return 0;
}
解决方法:
- 在类中定义同名成员,覆盖掉父类中的相关成员。
- 使用虚继承
class A
{
public:
int a;
};
class B1 : virtual public A // 虚继承
{
};
class B2 : virtual public A // 虚继承
{
};
class C : public B1, public B2
{
};
int main()
{
C c;
c.a = 10; // OK,不会有二义性了
return 0;
}
原理:使用虚继承时,C++ 编译器会做特殊处理,只会调用一个公共基类 A 的构造方法,这样就不会创建出多个同名变量 a 了。
二、次高准则:高效性
C++语言将高效性作为次高准则,是因为C++语言出现时,计算机性能普遍较低,因此计算机性能是一个重要的制约因素,为了运算效率而设计。高效性规则是为了保证程序的运行时间和储存空间尽可能节约而制定的,简化了程序的执行方式和过程,可以很好地避免时间和空间的浪费。
(1)数组下标0索引。
int a = new int [5];
这句话执行完毕后,机器都做了什么?
首先在内存就开辟了一块地址空间(内存中地址空间都是连续的,地址中的内容就是你存储在其中的值),然后变量a就指向了这片地址空间的“首”地址,如果想要访问这片地址的其他地址,那么就得用偏移量来计算,如下图:
变量a已经指向首地址,a[0] = a + 0 ,0代表的就是偏移量,a偏移0个单位,就是其本身,所以a[0]代表的就是第一个地址。 a[1] = a + 1; a偏移一个单位,那么就可以访问到a[1],也就是第二个地址。所以只需要 变量名+[偏移量] ,(如 a[0]) 就可以访问到相应的内存地址。这是下标从0开始的情况。
(2)逻辑短路。
这种规定使得程序运行效率大大提高,类似于刷题,我们做单选题时只要确定一个是正确答案,其他就不用再判断了。
如&&运算,当前面为0时,后面则不进行计算,发生短路;||运算,当前面为1时,后面则不进行计算,发生短路。
(3)编译器自主性:
编译器具有一定的自主性而可以自行选择先对函数中的哪个参数求值,这样做提高了编译器的效率。
(4)微调控制能力:
实际上,C与C++具有通常是汇编语言才具有的微调控制能力,可以根据具体情况微调程序以获得最大运行速度或最有效地使用内存。
三、第三准则:合乎日常习惯
程序设计语言最终是给程序员使用的。一套不合乎日常习惯的规则,会给程序员的使用带来极大的不便性。在保证逻辑和准确性的情况下,程序语言的设计应该尽量贴合程序员的日常习惯。
1.内置函数命名
大多编程语言都有自己的内置函数,在C++中的内置函数有数学函数、随机函数等。 内置函数的存在极大的提升了程序员的效率和程序的阅读,对于内置函数的命名往往是以这个函数的作用的英文命名的,符合日常习惯,在调用时也比较方便。
2.语序
不仅是C++,几乎所有编程语言的编译或运行顺序都是由左到右,由上到下。切合人们的阅读习惯。
不过在日常习惯的第三准则之前同样也要遵循前两个准则,例如标识符的优先级、结合性、逻辑短路等顺序。
四、相似相同规则
如果有两个不同的对象具有相似的行为,那么C++会为这两个对象额外增加对方的行为,使得这两个不同对象具有对方的相同行为。有利于程序的简化。
1.变量的初始化
如定义变量a等于5,一般是int a=5;
但也有int a(5); (括号的初始化,形似对象的初始化);还有int a={5};有int a{5};(形似数组的初始化);以及int a=({5});
2.数组的初始化:
给定数组b[10],其初始化一般为b[10]={ };(中括号内不填,则默认初始为0;也可以依次填入数据进行初始化)
C++11标准中,初始化可以直接b[10]{ };(数组与变量的相似相同规则)
3.对象的初始化
拷贝初始化:string str1 = “hello”;
使用等号(=)初始化一个变量,实际上执行的是拷贝初始化。编译器把等号右侧的初始值拷贝到新创建的对象中去,拷贝初始化通常使用拷贝构造函数来完成。
直接初始化:string str1(10,‘9’);
初始化方式本质上是C++将变量、对象及数组统一对待的原则的产物。