C++在C的基础上引入了面向对象编程思想,并丰富了许多实用的库和编程范式。对于已经熟悉C语言的学习者,掌握C++可以更好地理解如何弥补C语言在某些方面的不足,并对C语言设计中不够合理的地方进行优化,涉及到作用域、IO、函数、指针、宏等方面的改进。
本篇文章主要介绍了:C++是如何填补C语言语法的一些不足,以及介绍了C++如何优化C语言设计中不合理的地方,如作用域、IO、函数、指针、宏等方面的改进。
C++系统中预定义的、在语言或编译系统的实现中具有特殊含义的单词。下面我们只是看一下C++有多少关键字,不对关键字进行具体的讲解。后面我们学到以后再细讲。
C++(C++98)中总计63个关键字,其中包含了C语言的32个关键字,下面表格列举出了这63个关键字:
asm | do | if | return | try | continue |
---|---|---|---|---|---|
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
命名空间是用来组织和重用代码的。如同名字一样的意思,NameSpace(名字空间),之所以出来这样一个东西,是因为人类可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象,对于库来说,这个问题尤其严重,如果两个人写的库文件中出现同名的变量或函数(不可避免),使用起来就有问题了。由于C语言没有很好的办法解决这个问题,而C++为了解决这个问题,于是引入了名字空间这个概念
命名空间的定义使用关键字 namespace,后跟命名空间的名称,然后接一对{},在{}中定义变量/函数/类型。如下所示:
namespace Z // Z就是命名空间的名称
{
//命名空间的内容
int a = 10;
int b = 20;
....
int Add( int x, int y)
{
return x + y;
}
struct Stu
{
int name[20];
int id;
.....
};
}
上面是普通的命名空间,命名空间可以嵌套。如下所示:
namespace Z
{
int a;
int b = 20;
....
int Add( int x, int y)
{
return x + y;
}
namespace ZZB
{
int m = 10;
int n = 20;
int Sub(int x, int y)
{
return x - y;
}
}
}
另外,在同一个工程中还允许存在多个相同名称的命名空间,编译器最后会j将他们合并为同一个命名空间。
ps:
1.一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
2.命名空间里面的变量仍然是全局的,存储在静态区,命名空间只是起到一个隔离的作用,并不会改变其全局变量的属性。
首先定义一个命名空间:
namespace ZZB
{
int a = 10;
int b = 20;
int Add(int x, int y)
{
return x + y;
}
struct Node
{
......
};
}
那么我们该如何使用上面命名空间中的成员呢?下面就给出了命名空间的使用的三种方式。
语法格式:命名空间名称 + :: +成员名称
int main()
{
printf("a = %d\n", ZZB::a);
printf("b = %d\n", ZZB::b);
}
运行结果如下:
a = 10
b = 20
请按任意键继续. . .
语法格式:using 命名空间名称 + :: +成员名称
using ZZB::a;
int main()
{
printf("a = %d\n", a);
printf("b = %d\n", ZZB::b);
return 0;
}
运行结果如下:
a = 10
b = 20
请按任意键继续. . .
语法格式:using namespace 命名空间名称
using namespace ZZB;
int main()
{
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
运行结果如下:
a = 10
b = 20
请按任意键继续. . .
C++的输入和输出函数分别为:cin 和 cout。它们可以自动识别变量类型不需增加数据格式控制,比如:整形–%d,字符–%c。
使用时必须包含< iostream >头文件以及std标准命名空间。因为 cout 和 cin 是实现在std标准命名空间中的。
早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持
格式,后续编译器已不支持,因此推荐使用< iostream >+std的方式。
使用方式如下:
#include
using namespace std;//展开命名空间std
int main()
{
int a = 0;
int b = 0;
cin >> a >> b; //输入 a = 10, b = 20
cout << a << " " << b << endl;//endl代表换行
return 0;
}
结果如下:
10 20
10 20
请按任意键继续. . .
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
#include
using namespace std;//展开命名空间std
void print(int data = 0)
{
cout <<"data = " << data << endl;
}
int main()
{
print();//没有传参时,使用默认值
print(100);//传参时,使用指定实参
return 0;
}
结果如下:
data = 0
data = 100
请按任意键继续. . .
声明或定义函数时为函数的每一个参数都指定一个默认值。
#include
using namespace std;//展开命名空间std
int Add(int x = 10, int y = 20, int z = 30)//每一个参数都指定一个默认值
{
return x + y + z;
}
int main()
{
Add();
return 0;
}
是声明或定义函数时为函数的部分参数指定一个默认值,需要注意的是半缺省参数必须从右往左依次来给出,不能间隔着给。
#include
using namespace std;//展开命名空间std
int Add(int x, int y = 20, int z = 30)//部分参数都指定一个默认值
{
return x + y + z;
}
int main()
{
Add();
return 0;
}
// test.h
void print(int data = 0)
//test.c
void print(int data = 10)
{
cout <<"data = " << data << endl;
}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
详情参看如下文章:C++入门(基础语法)之函数重载
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
注意:引用类型必须和引用实体是同种类型的
语法:类型& 引用变量名(对象名) = 引用实体
注意:引用类型必须和引用实体是同种类型的,这是为了保证在使用引用时类型匹配,避免潜在的错误。
在这个例子中,b声明为 double 类型的引用,但试图引用一个 int 类型的变量a,这是不允许的。
int main()
{
int a = 10;
double& b = a;
return 0;
}
一旦引用被初始化为引用某个实体,它在其生命周期内将一直引用该实体,而不能再引用其他实体。这是引用的特性之一,称为引用的一次性绑定,也就是引用不能改变其指向。
int main()
{
int a = 10;
int b = 20;
int& c = a;//引用a
cout << "c: "<< c << endl;
c = 30;//修改的是a的值,因为c引用的是a
cout << "a: " << a << " c: " << c << endl;
int& c = b;//错误,一旦引用被初始化为引用a,就不能再引用其他实体b
cout << c << endl;
return 0;
}
常引用是指通过 const 关键字声明的引用,它用于指示引用的目标对象是常量,即不可被修改。
常引用的语法形式为:
#include
using namespace std;
int main()
{
const int a = 10;
//int& ra = a; //编译报错
const int& b = 10;//引用常量
//int& rb = 10;//编译报错
const int& c = a;//引用 const 修饰的常变量
return 0;
}
以下代码是使用引用来交换两个对象的值:
#include
using namespace std;
void Swap(int& e1, int& e2)
{
int tmp = e1;
e1 = e2;
e2 = tmp;
}
int main()
{
int a = 10;
int b = 20;
cout << "交换前:a = " << a << " b = " << b << endl;
Swap(a, b);
cout << "交换前:a = " << a << " b = " << b << endl;
return 0;
}
引用作为函数返回值是 C++ 中的一种特性,它允许函数返回一个对象的引用,而不是对象的拷贝,这样可以避免返回大型对象时的拷贝开销,从而提高效率,由于返回的是对象的引用,因此也有了对返回对象的修改能力。
下面详细介绍了引用作为函数返回值的一些情况和用法:
代码如下:
#include
#include
using namespace std;
//获取pos位置的元素
int& Get(int* nums, int n, int pos)
{
assert(pos >= 0);
assert(pos < n);
return nums[pos];
}
int main()
{
int nums[] = { 1, 2, 3, 4, 5 };
int n = sizeof(nums) / sizeof(nums[0]);
for (int i = 0; i < n; ++i)
{
if (Get(nums, n, i) % 2 == 0)
{
Get(nums, n, i) *= 2;
}
}
for (int i = 0; i < n; ++i)
{
cout << nums[i] << ' ';
}
cout << endl;
return 0;
}
例如下面的代码:
Add 函数返回了对临时变量 c 的引用,而 c 是一个局部变量,在函数调用结束后会被销毁,引用会指向一个不再存在的对象,导致访问无效的内存。因此,在 main 函数中使用返回的引用 ret 访问值时,会导致未定义行为,输出结果是不确定的(取决于编译器清不清理内存)。
//错误示范
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
再来看一个例子:
代码如下:
#include
using namespace std;
int& Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int& ret1 = Add(1, 2);
int& ret2 = Add(3, 4);
return 0;
}
我们通过调试窗口来监视 ret1 和 ret2 值的变化。
下面我们画一下函数调用栈帧图,来理解一下:
为避免这种问题,应该确保返回的引用指向的对象在函数调用结束后仍然有效,可以通过返回
1.函数参数的引用,2.返回静态变量或全局变量的引用,3.返回动态分配内存的指针,并在适当的时候释放内存等方式来实现。
引用概念上定义一个变量的别名,它们在内部表示为原变量的另一个名称。而指针存储一个变量地址,允许直接访问该地址的变量。
引用在定义时必须初始化,指针没有要求,而指针可以在任何时候进行初始化,也可以为空或未初始化。
引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。一旦引用被初始化为某个实体,它将一直引用该实体。指针可以在运行时被重新赋值,指向不同的对象。
没有空引用,但有 NULL 指针,引用必须指向一个已经存在的对象,因此没有空引用的概念。而指针可以具有 NULL 值,表示它不指向任何有效的对象。
在 sizeof 中含义不同。引用的 sizeof 结果为引用类型的大小,而指针的 sizeof 结果始终为4个或者8个字节。
引用自加1即引用的对象增加 1,指针自加1即指针向后偏移一个类型的大小,指向下一个相同类型的变量。
有多级指针,但是没有多级引用。指针可以指向另一个指针,这就是多级指针。而引用只能是一级的,不能指向另一个引用。
访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
引用比指针使用起来相对更安全。引用通常更直观和安全,因为它们在初始化后不能被重新赋值,不会出现野指针或悬空指针的问题。
内联函数是 C++ 中的一种被 inline 关键字修饰的函数,它通常用于提高程序的执行效率。内联函数的特点是在每次调用时将函数体的代码直接嵌入到调用位置,而不是通过函数调用的方式进行执行。这样可以减少函数调用建立栈帧的开销,从而提高程序的执行效率。
在C++中,一般是不建议使用宏了,因为宏有如下缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查
因此在C++中采取的解决办法是定义常量用 const 或者 enum,定义短小函数用inline。
至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!