C++入门,基于C语言基础(1)

本章目标

1. C++关键字
2. 命名空间
3. C++输入&输出
4. 缺省参数
5. 函数重载


C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等。熟悉C语言之后,对C++学习有一定的帮助,本章节主要目标:
1. 补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用域方面、IO方面、函数方面、指针方面、宏方面等。
2. 为后续类和对象学习打基础。

1、C++关键字(C++98)

c++一共有63个关键字,C语言有32个,这里只是大概了解一下,具体的学习在后面的章节。

2. 命名空间

C++和C的第一个区别C++程序的文件后缀为.cpp。

C++能够兼容C绝大部分的语法,因此大部分C程序在C++中也能够运行。

按照传统惯例,C++的起步程序仍然是一个输出"hello world"的程序。以下是一个标准的C++语言实现的程序。

接下来,我们来理解一下这个程序,可以看到两段代码大概有几处地方不同,这里首先我们要引入一个C语言没有的概念 ,就是命名空间,那么为什么C++需要命名空间呢?

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称都将存在于全局作用域中,可能会导致很多冲突,命名空间namespace关键字的出现就是针对这种问题的。使用命名空间的的是对标识符的名称进行本地化,以避免命名冲突或名字污染。

#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
 printf("%d\n", rand);
return 0;
}
// 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”

上述代码编译后会报错:error C2365: “rand”: 重定义;以前的定义是“函数”。

在C/C++标准库头文件<stdlib.h>或<cstdlib> 中,"rand"被用作一个内置函数,用于生成伪随机数。然而,在上面的程序中,将一个变量命名为"rand",这导致了命名冲突。编译器会认为你试图重新定义已经存在的关键字或函数,因此会抛出错误。

为了解决这个问题,C语言采用的是避免使用和关键字或内置函数重名的标识符作为变量名。但是,在实际使用中,有时候使用的是第三方库,我们并不知道有什么自定义的函数或者关键字,或者在同一个项目组的其他成员出现命名冲突的问题,为了彻底地解决这个问题,祖师爷在C++中提出了命名空间namespace关键字来解决这个问题。

命名空间的定义

C++中有四种域:全局域,局部域,命名空间域,类域。今天要讲命名空间前,我们会首先介绍一下命名空间域。

我们知道,在不同的域中可以定义同名变量,即可以在全局域和局部域都定义一个名为x的变量,那么如果想在全局域定义同名的变量,或者是想避免在全局域定义了同名变量后发生冲突的问题,C++中提出了命名空间namespace关键字来解决这个问题,命名空间的结构看起来和结构体相似,但是定义的是域,用法如下:

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

注意:在最末尾右花括号的后面不需要加上分号。

namespace zxt
{
 // 命名空间中可以定义变量/函数/类型
     int rand = 10;
     int Add(int left, int right)
     {
         return left + right;
     }
     struct Node
     {
         struct Node* next;
         int val;
     };
}

 命名空间可以多层嵌套。

// test.cpp
namespace zxt1
{
     int a;
     int b;
     int Add(int left, int right)
     {
         return left + right;
     }
     namespace N2
     {
         int c;
         int d;
         int Sub(int left, int right)
         {
             return left - right;
         }
     }
}

同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。只要内部不冲突就可以。

// ps:一个工程中的test.h和上面test.cpp中两个zxt1会被合并成一个
// test.h
namespace zxt1
{
    int Mul(int left, int right)
     {
         return left * right;
     }
}

注意

命名空间只能定义在全局,且不会改变变量的生命周期,只会影响访问。一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

命名空间的使用

命名空间的使用有三种方式:

1.加命名空间名称及作用域限定符
namespace zxt{
    int x=1;
}

int main(){
    printf("%d",x);
    return 0;
}

在上述代码中,无法访问到变量x的原因是编译器的搜索规则。

编译器首先搜索当前局部作用域,如果找不到,则继续搜索全局作用域。如果指定了特定的作用域,编译器将直接搜索指定的作用域。如果最终仍然找不到变量,编译器就会报错。

可以将这个搜索规则类比为在田里挖菜的例子。假设中午要炒菜,首先我们会去自己家的菜地找菜,这就是当前局部作用域的搜索。如果我们在自己家的菜地找不到菜,我们会去野生菜地找,这就是全局作用域的搜索。如果你知道隔壁老王家有菜,就可以直接去老王家的菜地找,这就是指定作用域的搜索。

那么,如何指定访问命名空间域内的变量,我们可以使用域作用限定符来指定要搜索的域,::是域作用限定符,用法即 命名空间名::变量名 ,左边为要访问的域,右边为要访问的变量,如果左边参数为空,即默认访问全局域。这样我们就可以访问到命名空间内的变量。

namespace zxt1{
    int x=1;
}
namespace zxt2{
    int x=2;
}
int main(){
    printf("%d",zxt1::x);
    printf("%d",zxt2::x);
    return 0;
}

这样一来,上面我们定义全局变量rand与库中定义的rand函数冲突的问题也得到了解决。

#include <stdio.h>
#include <stdlib.h>
namespace zxt{
    int rand = 10;
}
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
    printf("%d\n", zxt::rand);
    return 0;
}

命名空间域实现了在同一个域定义同名变量的功能,解决了命名冲突的问题,并提高代码的可读性和可维护性。

命名空间内函数的使用和变量一样,即 命名空间::函数调用,结构体的使用不太相同,结构体的调用如下:

//定义命名空间内的结构体
namespace MyNamespace {
    struct MyStruct {
        // structure members
    };
}
//在代码中使用命名空间内的结构体

int main() {
    struct MyNamespace::MyStruct myObject;
    // 使用myObject进行结构体操作
    return 0;
}
2.使用using将命名空间中某个成员引入

可以使用using关键字将命名空间中的某个成员引入到当前作用域,从而使访问该成员时无需使用命名空间限定符,相当于老王和所有人说,我家菜地的这种菜大家都可以摘,将该种菜的采摘权限放开,让所有人都可以直接摘取。

using N::b;
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    return 0;    
}

3.使用using namespace 命名空间名称引入

将命名空间展开,即把该命名空间的访问权限打开,访问所有成员时都无需使用命名空间限定符,当然,指定访问也可以。相当于老王把他家的菜地放开了,所有人都可以去摘菜地所有的菜。


using namespce N;
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    Add(10, 20);
    return 0;    
}

注意:一般在项目中,不建议将命名空间名称直接引入展开。

当一个命名空间被展开时,其中的所有成员被引入到全局作用域中。这样,我们就可以直接访问该命名空间内的成员,而无需使用限定符。

下面是一个示例:

namespace A {
    int x = 1;
    void foo() {
        std::cout << "A::foo()" << std::endl;
    }
}

namespace B {
    int x = 2;
    void foo() {
        std::cout << "B::foo()" << std::endl;
    }
}

int main() {
    using namespace A;
    
    // 使用限定符访问命名空间内的成员
    std::cout << A::x << std::endl;  // 输出: 1
    A::foo();  // 输出: A::foo()

    // 展开命名空间后,无需限定符即可访问命名空间内的成员
    using namespace B;
    
    std::cout << x << std::endl;  // 输出: 2
    foo();  // 输出: B::foo()

    return 0;
}

在上面的示例中,在 main 函数中使用了 using namespace A;using namespace B; 语句来展开命名空间。展开后,可以直接访问 AB 命名空间中的成员,同时也可以使用限定符来访问命名空间内的成员。

注意命名空间展开后的编译器查找规则有所变化

当没有指定命名空间作为作用域限定符时,编译器会按照从内层到外层的顺序查找标识符。首先会在当前作用域(例如当前的局部作用域)中查找,如果没有找到,则会在更外层的作用域(例如全局作用域)中查找。

如果命名空间是展开的,也就是进行了命名空间的展开操作,编译器会同时在全局作用域和命名空间作用域内查找标识符。

当指定了某个命名空间作为作用域限定符时,编译器会直接在该命名空间的作用域内查找标识符。如果在指定的命名空间内找不到标识符,则会报错。

3. C++输入&输出

那么,将命名空间名称直接引入不就与直接定义全局的变量没有区别吗,那么究竟有什么意义呢?我们接着往下看。

说明:

1.<iostream>是C++中用来进行输入输出操作的标准库头文件。它包含了一些用于输入输出的类和函数的声明。

2.cout标准输出对象(控制台)和cin标准输入对象(键盘)是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream>头文件中。

3.<<是流插入运算符,>>是流提取运算符。

4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。

注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持。

在代码中,cout 报错的原因是因为 C++ 中将所有的库放在 std 命名空间中。因此,在使用它们之前,需要在代码中使用命名空间限定符 std:: 来指明它们的命名空间。

std命名空间的使用惯例
std是C++标准库的命名空间,如何展开std使用更合理呢?
1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
2.在项目开发中,建议使用 std:: 命名空间限定符来使用常用的库对象和类型,并使用 using std::cout 等方式来展开需要使用的对象。这样可以避免命名冲突问题,特别是在大型项目中容易出现。

总之,为了正确使用 C++ 标准库,建议在使用标准库对象和函数时使用 std:: 命名空间限定符。在不同的场景中,可以根据需要选择合适的展开方式。

<<与>>

<<是流插入运算符,在输出时与 cout 标准输出对象一起使用;>> 是流提取运算符,在输入时与 cin 标准输入对象一起使用。

#include <iostream>
using namespace std;
int main()
{
   int a;
   double b;
   char c;
     
   // 可以自动识别变量的类型
   cin>>a;
   cin>>b>>c;
     
   cout<<a<<endl;
   cout<<b<<" "<<c<<endl;
   return 0;
}

关于cout和cin还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等等。因为C++兼容C语言的用法,这些又用得不是很多,这里就不进行展开了。

4. 缺省参数

在C++中,缺省参数可以用于函数的参数列表,用来指定在调用函数时可以省略的参数的默认值。在函数定义或声明中,可以通过在参数名后面加上=符号,然后指定参数的默认值来定义缺省参数。

例如,下面是一个使用缺省参数的函数声明和定义的示例:

// 函数声明
void printMessage(std::string message = "Hello World");
 
// 函数定义
void printMessage(std::string message) {
    std::cout << message << std::endl;
}

在上面的例子中,printMessage函数的参数message被定义为一个缺省参数,其默认值为字符串"Hello World"。因此,在调用该函数时,可以选择省略参数,此时会使用默认值进行调用;或者可以提供实际的参数进行调用。

第一次调用时,省略参数,于是,函数使用默认值进行调用,字符串打印出来为默认值。而在第二次调用中,由于提供了实际的参数,所以字符串的值打印出来为实际的参数。

缺省参数可以分为全缺省和半缺省两种类型。

  1. 全缺省:全缺省指的是函数的所有参数都有默认值。
  2. 半缺省:半缺省指的是函数的部分参数有默认值。调用这样的函数时,可以省略部分参数,省略的参数使用默认值,而保留的参数需按顺序传入。

注意:缺省参数只能在函数的参数列表的末尾位置,不能在中间位置出现。如果一个函数的参数列表中某个参数被定义为缺省参数,那么该参数后面的所有参数都必须是缺省参数。否则,调用时可能会出现歧义或错误。

5. 函数重载

函数重载是指在同一个作用域内使用相同的函数名,但参数列表不同的多个函数。在C语言中不允许同名函数的存在,而C++中的函数重载是一种多态的表现形式,可以根据传入的参数类型和数量来选择合适的函数进行调用。

函数重载的特点:

  1. 函数名称相同。
  2. 参数列表不同,可以包括参数的类型、个数、顺序等方面的差异。
  3. 返回值类型可以相同也可以不同。
  4. 函数重载与函数的返回值类型无关,即不能仅根据返回值类型的不同来进行函数重载。
#include<iostream>

//参数个数不同
void fun1(int x,int y){}
void fun1(int x){}
void fun1(){}


//参数类型不同
void fun2(int x,double y){}
void fun2(int x,int y){}


//参数顺序不同
void fun3(int x,double y){}
void fun3(double x,int y){}

函数重载的实现方式: 函数重载通过编译器的名称修饰(name mangling)来实现。在编译过程中,编译器会根据函数名、参数类型、参数个数等生成一个唯一的函数符号,以区分不同的重载函数。

// 函数重载示例
int add(int a, int b)
{
    return a + b;
}

double add(double a, double b)
{
    return a + b;
}

int add(int a, int b, int c)
{
    return a + b + c;
}

int main()
{
    int result1 = add(3, 4);  // 调用第一个重载函数
    double result2 = add(2.5, 3.7);  // 调用第二个重载函数
    int result3 = add(1, 2, 3);  // 调用第三个重载函数
    return 0;
}
 

在上述示例中,add 函数被重载了三次,根据参数的不同选择了不同的函数进行调用。通过重载函数,可以提高代码的可读性和灵活性,使函数能够适应不同类型的参数。

注意:代码会报错,因为存在二义性。函数 fun1 有两个重载版本,一个带有一个整型缺省的参数,一个没有参数。在 main 函数中调用 fun1 时没有提供参数,编译器无法确定调用哪个版本的 fun1,因此会报错。

因此,在使用中,要注意避免这种情况。

好了,这篇博客到这里就结束啦!本文简单介绍了C++的关键字、命名空间、输入输出、缺省参数和函数重载,可以对C++的基础知识有一个初步的了解。

在下一篇博客中,我们可以继续深入探讨C++一些的更高级特性。

不管你是初学者还是有一定经验的开发者,学习和掌握C++都是一项有趣且具有挑战性的任务。希望我的博客能够为你提供一些帮助和指引,让你在C++的学习过程中不断进步和成长。

祝你在接下来的学习和实践中取得更大的成功!如果你有任何问题或者想要了解更多关于C++的内容,欢迎随时与我交流。谢谢阅读!

  • 60
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值