文章目录
- 前言
- 1.输入与输出
- 输出
- 输入
- cin和scanf的对比
- 2.命名空间
- 2.1namespace存在的意义
- 2.2namespace的使用
- 3.缺省参数
- 4.函数重载
- 重载函数的调用规则
- 5.引用
前言
我们先通过一段简单的代码来拉开C++的序幕;
//text.cpp
#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
printf("hello world");
return 0;
}
//int main()
//{
// cout << "hello world" << endl;
// return 0;
//}
在上述代码中,**printf(“hello world”);所实现的效果。与下面的cout << “hello world” << endl;**的效果相同,我们要知道在C++当中他是兼容C语言的语法的,所以代码中C语言的语法也能运行成功;
而在这里面就涉及到了我们下面要说的在C++中的输入与输出;
1.输入与输出
• < iostream > 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输
出对象。
• std::cin 是 istream 类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输
⼊流。
• std::cout 是 ostream 类的对象,它主要⾯向窄字符的标准输出流。
• std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
• <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移)
• 使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式, C++的输⼊输出可以⾃动识别变量类型其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出。
• cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们。
• ⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。
• 这⾥我们没有包含<stdio.h>,也可以使⽤printf和scanf, 因为在 < iostream >中间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
对于输入和输出两种操作来说,最重要的就是流插入运算符和流提取运算符;
输出
输出:cout << “hello world” << endl;
"<<"符号表示的是将该字符串传给cout(传的只是地址),该符号指出了信息流动的方向;而且它不单单可以只传输一段字符串,甚至是多段、不同类型的多次拼接传输。例如:
#include<iostream>
#include<stdio.h>
using namespace std;
//int main()
//{
// printf("hello world");
// return 0;
//}
int main()
{
cout << "hello world" << " " << "123" << endl;
return 0;
}
从结果中就可以验证我们上面所说的话;
而且在这里如果熟悉C语言的我们可以看出来流插入运算符,与我们C语言中的按位左移运算符相同,由于C++兼容C语言,区分就成了问题,所以就有了我们下面要讲的运算符重载
通过重载,同一个运算符将有不同的含义。编译器通过上下文来确定运算符的含义。C本身也有一些运算符重载的情况。例如,&符号既表示地址运算符,又表示按位AND运算符;*既表示乘法,又表示对指针解除引用。这里重要的不是这些运算符的具体功能,而是同一个符号可以有多种含义,而编译器可以根据上下文来确定其含义。C++扩展了运算符重载的概念,允许为用户定义的类型(类)重新定义运算符的含义。
输入
2.输入:cin >> a;
#include <iostream>
int main() {
int num;
std::cout << "Enter an integer: ";
std::cin >> num;
std::cout << "The number you entered is " << num << std::endl;
return 0;
}
这个示例中,我们使用 std::cin 来从键盘读取一个整数,并将该整数存储在变量 num 中。我们还使用 std::cout 向用户输出提示信息和最终的整数值。
cin 可以读取各种类型的输入值,包括整数、浮点数、字符和字符串。
cin和scanf的对比
cin 支持所有 C++ 内置类型的数据输入,包括 int, float, double 等。 它还支持用户定义的数据类型,例如类和结构。另一方面,scanf 函数只支持 C 语言的基本类型,如 int, char, float, double 等。它不支持 C++ 类和结构体。
错误处理:
cin 使用构造函数抛出异常来处理输入错误。当您尝试读取不合适的类型或不是具有所需范围的类型的值时,会抛出一个异常。这使得可以在程序的其他部分捕获该异常,并对用户做出适当的反应。另一方面,scanf 使用返回值来指示输入的成功与否,并且没有异常。
性能:1.cin 的性能可能比 scanf 高,因为前者通常使用循环和条件语句来处理输入,而后者通过从输入流中查找特定的字符进行输入。因此,使用 cin 可以更高效地读取大量的输入数据。
可读性和便携性:
2.cin 的语法和功能比 scanf 更易于理解和编写。 cin 使用各种工具,例如流插入和流提取符,使读取和写入数据的操作更直观。此外,cin 在 C++ 标准中定义,因此可在所有的 C++ 编译器中使用。
2.命名空间
2.1namespace存在的意义
在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全局作⽤域中,可能会导致很多冲突。
使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名冲突或名字污染,
namespace关键字的出现就是针对这种问题的。
namespace(命名空间)是一种用于组织代码中命名的机制。它提供了一种方法来避免命名冲突,并允许您创建多个具有相同名称的独立实体。
定义命名空间
可以使用以下语法来定义命名空间并指定其名称:
//text.cpp
namespace my_namespace {
// 在这里定义命名空间的成员
}
我们通过一个C语言中错误的代码来理解:
#include <stdlib.h>
int rand = 10;
int main()
{
// 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”
printf("%d\n", rand);
return 0;
}
而在C++中我们可以通过在命名空间中定义它来更好的解决;
2.2namespace的使用
• 定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中
即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
• namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下
⾯的rand不在冲突了。
• C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/
类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响
编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
- namespace只能定义在全局,当然他还可以嵌套定义。
2.项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。
3.C++标准库都放在⼀个叫std(standard)的命名空间中。
在使用命名空间的成员时,我们需要使用名称空间的名称将其导入到当前范围。
#include<iostream>
using namespace std;
namespace study
{
//变量
int arr = 1;
//函数
int ADD(int left, int right)
{
return left + right;
}
//结构体
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
cout << study::ADD(1, 2) << endl;
return 0;
}
编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以
下⾯程序会编译报错。所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:• 1.指定命名空间访问,项⽬中推荐这种⽅式。
• 2.using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
• 3.展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。
// 指定命名空间访问
int main()
{
printf("%d\n", N::a);
return 0;
}
// using将命名空间中某个成员展开
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
//展开整个命名空间中的全部成员;
#include <iostream>
namespace my_namespace {
void some_function() {
std::cout << "Hello from my_namespace!" << std::endl;
}
}
using namespace my_namespace;
int main() {
some_function();
return 0;
}
这里还有一个问题,当我们有两个命名空间,它们都定义了一个名为 sayHello 的函数。
// 在命名空间 `test` 中定义 `sayHello` 函数
namespace test {
void sayHello() {
std::cout << "Hello from test!" << std::endl;
}
}
// 在命名空间 `test2` 中定义 `sayHello` 函数
namespace test2 {
void sayHello() {
std::cout << "Hello from test2!" << std::endl;
}
}
int main() {
using namespace test;
using namespace test2;
sayHello();
return 0;
}
在这个示例中,我们使用 using 语句导入了 test 命名空间中的 sayHello 函数和 test2 命名空间中的 sayHello 函数。当我们调用 sayHello 函数时,C++ 编译器并不知道是调用 test::sayHello 还是 test2::sayHello。 在运行期间与两个函数冲突。
为了避免这种情况,您可以在每个命名空间中导入只自己需要的成员,或者在使用命名空间成员时显式引用命名空间,以确保编译器知道调用哪个函数。例如,
这时的解决方法就是显式引用了每个命名空间中的 sayHello 函数,以确保编译器知道我们正在调用哪个函数:
#include <iostream>
namespace test {
void sayHello() {
std::cout << "Hello from test!" << std::endl;
}
}
namespace test2 {
void sayHello() {
std::cout << "Hello from test2!" << std::endl;
}
}
int main() {
test::sayHello();
test2::sayHello();
return 0;
}
3.缺省参数
• 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参
则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把
缺省参数也叫默认参数)
• 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左
依次连续缺省,不能间隔跳跃给缺省值。
• 带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
• 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
#include <iostream>
#include <assert.h>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使⽤参数的默认值
Func(10); // 传参时,使⽤指定的实参
return 0;
}
#include <iostream>
using namespace std;
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1, 2);
Func1(1, 2, 3);
Func2(100);
Func2(100, 200);
Func2(100, 200, 300);
return 0;
}
4.函数重载
C++支持在同一作用域中定义多个同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
// 返回值不同不能作为重载条件,因为调⽤时也⽆法区分
//void fxx()
//{}
//
//int fxx()
//{
// return 0;
//}
// 下⾯两个函数构成重载
// f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
重载函数的调用规则
1.参数数量(或参数的顺序)应与函数定义匹配。
2.参数类型应与函数定义中参数类型匹配(考虑到 C++ 的类型转换基本原则)。如果函数定义中提供有默认的参数值,则重载函数调用时可以省略该描述符所引用的参数。
#include <iostream>
// 无参数的原型
void sideFunction() {
std::cout << "Call with no parameters" << std::endl;
}
// 双参数原型,整型和双精度型参数
void sideFunction(int num, double num2) {
std::cout << "Call with int and double arguments: " << num << " " << num2 << std::endl;
}
int main() {
sideFunction(); // 调用无参数版本的 sideFunction()
sideFunction(10, 3.14); // 调用 int 和 double 参数版本的 sideFunction()
return 0;
}
5.引用
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,
它和它引⽤的变量共⽤同⼀块内存空间。
形式表现为:类型& 引用别名 = 引用对象;
并且它可以有多个引用别名;
int& a = b;
int& c = a;
此时a是b的引用别名;(**不要把引用符号“&”和取地址符号混淆**)
对别名a取别名c,c相当于还是a的别名;
它是与变量相关联的指针,但与其他指针不同,引用必须初始化指向某个已存在的对象,并且在整个其生命周期中都必须指向那个对象。引用就像指针一样,但是它们的一些优点是:
1.引用不像指针那样需要显式解引用操作。引用允许直接访问其引用的值,而不需要使用""运算符。
2.由于引用必须在初始化时被绑定到某个对象,因此不会出现悬空引用问题。
3.由于引用初始化不能更改,因此避免了错误的引用传递问题,即在函数调用传递引用时未提供有效的引用
引用必须在定义的同时初始化,并且在之后的程序里不能改变引用的指向;
对于这句话的理解,我们看下面一段代码;
#include<iostream>
using namespace std;
int main()
{
int a = 10;
// 编译报错:“ra”: 必须初始化引⽤
//int& ra;
int& b = a;
int c = 20;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值
b = c;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
这里面的b = c并非改变了引用指向,而是赋值,因为如果是改变了引用指向,根据我们上面给出的定理,引用别名和变量共用一块地址,所以我们只需要看它们的地址是否相同就能够判断出;
从地址中我们可以看出b 和 c 的地址并不相同,所以b = c只是赋值;
而下面这段代码中的形式,才是改变了引用指向;
#include <iostream>
using namespace std;
int main() {
int a = 99, b = 32;
int &r = a;
a = 67;
cout << a << ", " << r << endl; //67,67
&r=b;//不能重新引用其他数据
b = 88;
cout << b << ", " << r << endl;
return 0;
}
结果会报错为表达式必须为可修改的左值;
最后对于引用中另外的关键知识点和问题,后面我会再写一篇文章专门讲解