引入说明
C++在设计上兼容了C语言的大部分语法特性,因此许多C语言代码可以在C++环境中直接编译运行。为了使用C++编译器进行编译,通常需要将源文件的后缀名改为.cpp。当Visual Studio等开发环境检测到.cpp扩展名时,会自动调用C++编译器进行处理。
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
以下所示的C语言风格代码,在C++中同样可以成功编译和执行。不过值得注意的是,C++提供了一套更为现代化的输入输出机制,逐渐取代了传统的printf和scanf方式。
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
接下来的内容将围绕C++的基础语法展开讲解,首先从命名空间开始。
命名空间(namespace)
1. 命名空间的作用与价值
在C和C++程序中,变量、函数以及类的数量往往非常庞大,这些标识符默认都位于全局作用域中,容易引发命名冲突问题。命名空间的引入正是为了解决这类名称污染和冲突现象。通过namespace关键字,可以将一组相关的标识符组织在一个独立的作用域内,实现局部化管理。
例如,在C语言环境下,以下情况极易导致编译错误:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
该代码在编译时会报错,原因在于rand在标准库中已经被定义为一个函数名,若用户再次定义同名变量则会产生冲突。
2. 命名空间的定义方式
定义命名空间需使用namespace关键字,后接命名空间名称,再用一对花括号{}包裹其内部成员。命名空间中可包含变量、函数、类型等多种元素。
命名空间只能在全局范围内定义,支持嵌套结构。C++标准库中的所有组件均被封装在名为std(standard的缩写)的命名空间中。
本质上,命名空间创建了一个独立的作用域,与全局域相互隔离。不同命名空间下允许存在相同名称的变量或函数,从而避免了冲突。例如,下面的例子中rand就不会与标准库中的rand发生冲突。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
namespace example
{
int rand = 10;
}
int main()
{
printf("%p\n", rand);//默认是访问的是全局的rand函数指针
printf("%d\n", example::rand);//指定访问example命名空间里的rand
return 0;
}
在介绍如何使用命名空间之前,先了解作用域限定符(::)。该操作符由两个冒号组成,用于指定访问某个特定命名空间中的成员。
命名空间的使用方法
命名空间支持嵌套定义,并可通过作用域解析符逐层访问其内部成员。
#include <stdio.h>
namespace EX
{
namespace ex1
{
int rand = 10;
}
namespace ex2
{
int rand = 20;
}
}
int main()
{
printf("%d\n", EX::ex1::rand);
printf("%d\n", EX::ex2::rand);
return 0;
}
using关键字的应用
通过using namespace std;语句,可以将指定命名空间中的所有成员“展开”,使其在当前作用域中无需加前缀即可直接使用。这在日常练习中较为常见,尤其是在频繁使用cin、cout等标准库对象时。
#include <stdio.h>
namespace EX
{
namespace ex1
{
int rand = 10;
}
namespace ex2
{
int rand = 20;
}
}
using EX::ex1::rand;
int main()
{
printf("%d\n", rand);
return 0;
}
然而,在大型项目开发中,不推荐广泛使用using namespace std,因为它可能导致命名污染。此外,如果未正确展开所需命名空间,编译器将无法识别如rand等标识符,从而报错提示找不到声明。
C++输入与输出机制
<iostream> 是 Input Output Stream 的缩写,属于C++标准输入输出流库,其中定义了基本的输入输出对象。
std::cin是 istream 类的一个实例,主要用于处理窄字符(char类型)的标准输入。std::cout是 ostream 类的对象,负责窄字符的标准输出。std::endl实际上是一个函数,插入输出流时相当于添加换行符并刷新缓冲区。
运算符 << 被重载为流插入操作符,而 >> 则作为流提取操作符使用。需要注意的是,这两个符号在C语言中原本用于位运算中的左移和右移操作。
C++的IO机制更加便捷,相比C语言中使用printf和scanf必须显式指定格式控制符,C++能够自动识别变量的数据类型(这一特性基于函数重载实现),更重要的是它能良好地支持自定义类型的输入输出操作。
由于IO流涉及类、对象、运算符重载、继承等一系列面向对象的核心概念,而这些内容尚未深入讲解,因此目前我们仅作初步认识。后续章节将专门详细剖析IO流库的设计与应用。
像cout、cin、endl等均属于C++标准库的一部分,它们统一位于std命名空间中,因此在使用时需配合命名空间访问方式(如std::cout)或通过using声明简化调用。
在一般练习场景中,使用using namespace std;是可接受的;但在实际工程项目中,建议避免整体展开标准命名空间,以防止潜在的命名冲突。
值得注意的是,即使没有显式包含<stdio.h>头文件,依然可以在程序中使用printf和scanf函数。这是因为某些编译器(如VS系列)在包含<iostream>时会间接引入C标准I/O函数。但其他编译器可能不具备此行为,可能会导致链接错误。
缺省参数(Default Parameters)
缺省参数是指在函数声明或定义时为其形参设定一个默认值。当调用该函数时,若未传入对应实参,则自动采用预设的默认值;否则使用调用者提供的实际参数。缺省参数可分为全缺省和半缺省两种形式(部分资料也称其为默认参数)。
全缺省指的是所有参数均设有默认值,而半缺省则是仅对部分参数设置默认值。根据C++语言规范,半缺省参数必须从右往左连续设置,不允许跳跃式地设定默认值。例如:
//正确形式(半缺省)
int ADD(int a,int b=10,int c=20)
//错误形式
int ADD(int a=10,int b,int c=20)
对于带有缺省参数的函数,在调用时也必须遵循从左到右依次传递实参的原则,不能跳过前面的参数只传后面的。
当函数的声明与定义分离时,缺省参数只能出现在函数声明中,不能同时在声明和定义中重复出现。这是C++的规定,旨在避免歧义和重复定义问题。
#include <iostream>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func();//不带参数,使用缺省值
Func(10);//给出实参,则使用实参
return 0;
}
函数重载(Function Overloading)
C++允许在同一作用域内存在多个同名函数,只要它们的参数列表不同即可构成重载。差异可以体现在参数个数、参数类型或参数顺序上。
//参数类型不同
int ADD(int x, int y)
{
return x + y;
}
double ADD(float x, float y)
{
return x + y;
}
//参数个数不同
void Func()
{
cout << "Func()" << endl;
}
void Func(int a)
{
cout << "Func(a)" << endl;
}
//参数顺序不同
void Func(int x, char y)
{
//....
}
void Func(char y, int x)
{
//....
}
需要特别注意:函数的返回类型不能作为重载的判断依据,因为在函数调用时,仅凭返回值无法确定应调用哪一个版本。
//void f()
//{
//
//}
//int f()
//{
//
//}
尽管函数重载提供了灵活性,但在实际使用中也可能遇到调用歧义的问题。
void Func()
{
cout << "Func()" << endl;
}
void Func(int a)
{
cout << "Func(a)" << endl;
}
int main()
{
Func();
return 0;
}
上述代码中,编译器无法明确选择调用哪个函数,从而产生二义性错误。解决此类问题的一种有效方式是将不同的函数置于不同的作用域(如命名空间)中,以消除冲突。
引用(Reference)
1. 引用的基本概念与定义
引用并不是创建一个新的变量,而是为已存在的变量提供一个别名。在编译过程中,编译器不会为引用分配独立的内存空间,它与其所引用的变量共享同一块内存区域。其语法格式如下:
类型& 引用别名 = 引用对象;
通过以下示例可以更清楚地理解引用的概念:
int main()
{
int a = 0;
int& b = a;
int& c = a;//b和c是a的别名
int& d = c;//d是c的别名,相当于是a的别名
return 0;
}
在此例中,变量 a、b、c 和 d 实际上共享同一块内存空间。
引用具有以下几个重要特性:
- 引用在定义时必须进行初始化,不允许出现对空值的引用。
- 同一个变量可以拥有多个引用。
- 一旦某个引用被初始化后,它只能绑定到一个特定的实体,不能重新指向其他对象。
int main()
{
int a = 10;
int& ra = a;
int c = 20;
int b = c;//这里要分清楚,并非让b引用c,这是一个赋值,只是一个临时拷贝。
return 0;
}
const 引用
在学习引用的过程中,const 引用是一个容易引起混淆的概念。为了准确理解,我们需要明确以下两点规则:
- 在发生类型转换时,系统会生成一个临时对象来保存转换后的值。根据 C++ 标准,这些临时对象具有“常量属性”,即它们是 const 类型的。
- 在引用过程中,目标对象的访问权限可以被缩小,但绝不能被放大。例如,可以用 const 引用去绑定非 const 对象,但反过来则不被允许。
下面通过代码示例进一步说明:
int main()
{
int a = 10;
int b = 20;
int& f = a + b;//这样会报错,因为a+b返回一个值的时候会产生一个临时对象,这个临时对象是常量,如果按照前面这么写就将
//权限放大了,变成了既可读,又可写。所以,我们要在前面加一个const修饰,const int& f=a+b;
return 0;
}
int main()
{
const int a = 10;
int& b = a;//同样是权限放大
return 0;
}
int main()
{
int a = 0;
double& b = a;//类型转换,应该用const修饰
return 0;
}
指针与引用的关系对比
- 引用在底层实现上并不需要额外开辟内存空间,而指针作为独立变量,需要占用存储地址的空间。
- 引用在定义时必须绑定到一个有效的对象,即必须初始化;而指针虽然建议初始化,但在语法上允许未初始化的存在。
- 引用一经绑定便不可更改,始终指向初始对象;而指针可以在运行期间多次修改,指向不同的对象。
- 引用可以直接操作其所代表的对象,无需特殊操作符;而指针必须通过解引用(*)才能访问目标数据。
- 在使用 sizeof 运算符时,引用返回的是其所引用实体的实际大小;而指针无论指向何种类型,sizeof 的结果均为当前系统地址总线宽度对应的字节数(通常为 4 或 8 字节)。


雷达卡


京公网安备 11010802022788号







