【c++初阶】c++和c语言到底有何不同,万字爆肝整理c++入门篇,小白快来!

img博客主页:一个努力敲代码的小粉象

img系列专栏:《c++初阶》

img:苟日新,日日新,又日新。

感谢大家的点赞 img评论img收藏img

前言

今天我们正式开始c++的零基础学习,相信很多学习计算机的同学们都会或多或少对c语言和c++之间的区别有一定的兴趣,但苦于知识琐碎,无法及集中整理,本篇博客将从零基础讲起,将c++和c语言语法的不同一一列举。

c++是在c的基础上,融入进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等等。本博客的主要目标:

补充c语言语法的不足,以及c++是如何对c语言设计不合理的地方进行优化的,比如:IO方面,函数方面,指针方面,宏方面等等。

命名空间

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

这里,我们来看一看c语言出现的命名冲突的问题:

这里为什么会失败呢?是因为我们定义的变量rand头文件中的函数出现了冲突,导致了失败。

所以,c++为了弥补c语言的命名冲突问题,定义了命名空间。

命名空间定义

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

1.注意,命名空间中可以定义变量、函数、类型

namespace FBL
{
    int rand=0;
    int Add(int left,int right)
    {
        return left+left;
    }
    sturct Node
    {
        struct Node* next;
        int val;
    }
}

2.命名空间可以嵌套

namespace N1
{
    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;
        }
    }
}

3.同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

4.注意,一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

命名空间的使用

我们现在来看看,直接对命名空间中的变量进行引用,会出现什么呢?

我们发现,这里出现了编译的报错。

所以我们在使用命名空间时是不能直接进行引用的,接下来我们将介绍以下三种方式

  • 加命名空间名称及作用域限定符 ( :: )

    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;
    }
    

    注意,该方式是指定展开,在工程中使用更加适合

  • 使用using namespace命名空间名称引入

using namespace N;
int main()
{
    //printf("%d\n", N::a);
    //printf("%d\n", b);
    int add=Add(10, 20);
    cout << add << endl;
}

c++输入&输出

我们在学习c语言的过程中,输入和输出函数分别是scanf和printf这两个函数,但在c++中,我们要引入新的符号:cout和cin。

先来看看Hello,world!用c++该怎么实现吧。

感受来自c++的初次问候吧!!

#include<iostream>
using namespace std;
int main()
{ 
    cout<<"Hello,world!"<<endl;
    reutrn 0;
}

说明:

1.使用**cout标准输出对象(控制台)cin标准输入对象(键盘)**时,必须包含头文件,以及按命名空间使用方法使用std;(类比c语言当中的stdio.h)

2.cout和cin是全局的流对象,endl是特殊的c++符号,表示换行输出,他们都包含在头文件中。

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

4.cout/cin的输入输出更加的方便,不需要像printf/scanf一样,需要手动控制格式。c++的输入输出可以自动识别变量类型。

5.实际上cout和cin分别是ostream和istream类型的对象,>>和<<也设计运算符重载的知识,这个知识我们在后续的博客当中还会着重讲解,这里我们先简单学习他们的应用。

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

这里我们来一一实现一下上文当中提到的cout和cin,来看看c++的输入和输出的优越性。

int main()
{
    int a;
    double b;
    char c;

    //可以自动识别变量的类型
    cin >> a;
    cin >> b >> c;
    cout << a << endl << b << endl << c << endl;
    return 0;
}

std命名空间的使用惯例

std是c++标准库的命名空间,如何展开std使用更合理呢?

1.在日常练习当中,建议直接使用using namespace即可,这样就很方便。

2.using namespace std展开,标准库就会全部暴露出来,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习当中很少出现,但是项目开发中代码较多、规模较大,就会容易出现,所以建议在项目开发中使用指定命名空间+using std::cout展开常用的库对象/类型等方式。

缺省函数

缺省函数概念

缺省函数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

void Func(int a=0)
{
    cout<<a<<endl;
}
int main()
{
    //没有传参时,使用参数的默认值
    Func();
    //传参时,使用指定的实参
    Func(10);
    
    return 0;
}
缺省参数的分类
  • 全缺省参数

    void Func(int a = 0)
    {
        cout << a << endl;
    }
    int main()
    {
        //没有传参时,使用参数的默认值
        Func();
        //传参时,使用指定的实参
        Func(10);
    
        return 0;
    }
    
  • 半缺省参数

    void Func(int a, int b = 10, int c = 20)
    {
        cout << "a=  " << a << endl;
        cout << "b=  " << b << endl;
        cout << "c=  " << c << endl;
    }
    int main()
    {
        Func(15,30);
    }
    
  • 半缺省参数必须从右往左依次来给出,不能间隔给,容易引发歧义。

  • 如果声明和定义中同时出现缺省参数,而恰巧两个位置提供的值是不同的,那么编译器就无法确定倒一该用哪个缺省值,所以:缺省参数不能在函数声明和定义中同时出现,一般缺省参数在声明中出现一次即可

    注意:

    1.缺省值必须是常量或者全局变量

    2.c语言是不支持缺省参数的。

    函数重载

    自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。比如:以前有一个笑话,国足是“谁也赢不了。”兵乓球是“谁也赢不了。”

函数重载的概念

函数重载:是函数当中的一种特殊情况,c++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或者类型顺序)不同,常用来处理实现功能类似数据类型的不同问题。

这里我们要强调一下,函数重载无非满足两个条件其一,函数名相同,功能相似,其二,参数不同,其中又包括类型不同、个数不同而与返回值无关

下面我们通过代码来感受其中的不同

  //1.参数类型不同
  int Add(int left, int right)
  {
      return left + right;
  }
  double Add(double left, double right)
  {
      return left + right;
  }
//2.参数个数不同
void f()
{
    cout << "f()" << endl;
}
void f(int a)
{
    cout << "f(int a)" << endl;
}

至于为什么c++支持函数重载的原理,这与程序的编译、汇编、连接的编译过程有关,我们将会在之后的博客中向大家解答,敬请期待~~~~

引用

引用概念

引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

这里我们进行一个形象的比喻~~

李逵,在家被称为铁牛,江湖上人称黑旋风

再用图像辅助大家理解~~~

我们先在内存中开辟了一个空间a,然后又给这个一块空间取了一个别的名字,b。

所以,无论是a还是b,指向的均是这一块空间,a和b的地址是相同的

类型& 引用变量名(对象名)=引用实体

void TestRef()
{
    int a = 0;
    int& ra = a;// 定义引用类型

    printf("%p\n", &a);
    printf("%p\n", &ra);
}

int main()
{
    TestRef();
    return 0;
}

PS:引用类型必须和引用实体是同一种类型的

引用特性
  • 引用在定义时必须初始化,否则,

  • 一个变量可以有多个引用

  • 引用一旦引用一个实体,再不能引用其他实体。(这里就说明了,在c++中,引用不能改变指向)

    void TestRef()
    {
        int a = 0;
        //int& ra;//这里会出现编译错误,引用必须要初始化
        int& ra = a;
        int& rra = a;
        printf("%p %p %p\n", &a, &ra, &rra);
    }
    int main()
    {
        TestRef();
        return 0;
    }
    

我们可以看到,上述的a,ra和rra都是同一个地址,也就是说明一个变量可以有多个别名。

常引用

我们先复习一个关键字const,const用来定义常量,如果一个变量被const修饰,那么他的值就不可以被改变

  • 权限的放大,编译错误

    void TestConstRef()
    {
        const int a = 10;
        int& ra = a;
    }
    
    int main()
    {
        TestConstRef();
    }
    

这里其实也很好理解,进行一个类比,比如李逵的妈妈不允许李逵去喝酒,结果看到李逵去喝酒,气哄哄地问李逵为啥去喝酒,李逵说:"不让喝酒的是李逵,现在在喝酒的是铁牛。"大家是不是感觉到了很荒唐?所以,权限的放大就会导致编译错误。此时,我们在int&前加上const进行权限的限制,便没有问题了。

  • 权限可以缩小,编译没有问题

    void TestConstRef()
    {
         int a = 10;
         const int& b=a;
    }
    
    int main()
    {
        TestConstRef();
    }
    

这里大家也可以去类比一下**”李逵喝酒“**这个例子,能很快就想通~~

  • 类型不同,编译错误

    void TestConstRef()
    {
        double a = 13.14;
        int& ra = a;
    }
    
    int main()
    {
        TestConstRef();
    }
    

这里很好理解,引用的类型必须和实体相同

但是,我们来看看下面这个代码~~~

void TestConstRef()
{
    double a = 13.14;
    const int& ra = a;
}

int main()
{
    TestConstRef();
}

这里大家是不是会大吃一惊,为什么加上一个const就会成功了呢?

下面我们就来讲讲这其中的奥妙~~

首先,我们先要明确两个可能会被遗漏的知识点:

其一,在进行类型转换(强制类型转换,隐式类型转换)的时候,是会将变量储存到临时变量中;

其二,临时变量具有常性。

下面我们还是通过图像来辅助理解一下~~~

也就是说,在进行类型转换的时候,原本的数据类型是不发生改变的,而是拷贝出一份临时变量,将临时变量的类型进行改变,而拷贝出的临时变量是具备常属性的,所以需要在前加上const来表示其常属性。

使用场景
  • 做参数

    我们还记得,当初学习c语言的时候,强调的形参和实参的经典问题,形参是实参的拷贝,单纯交换形参的值,是没有改变实参的,所以我们选择了传址的方法进行交换。而今天,我们在c++中实现两个数据的交换,则用到了引用,通过刚刚的了解我们知道了引用和实体都是指向同一块位置,所以使用了下面这个代码实现~~~

    void Swap(int& left, int& right)
    {
        int tmp = left;
        left = right;
        right = tmp;
    }
    int main()
    {
        int a = 3;
        int b = 4;
        Swap(a,b);
        cout << a << endl << b << endl;
    }
    
  • 做返回值

    int count()
    {
        int n = 0;
        n++;
        return n;
    }
    
    int main()
    {
        int ret = count();
        cout << "ret= " << ret << endl;
        return 0;
    }
    

    这个情形我们在C语言的学习中经常见到,但是这里就要提出一个疑问了——这里的返回值是n本身还是一个关于n的拷贝呢?

    这里就需要联系函数栈帧来学习了。

这里我们发现,当count函数结束之后,函数栈帧就会被销毁,其中n返回值其实是n的一个拷贝。

那么我们这里发生一点点变化,返回值用引用,又会发生什么呢?

这里我们发现出现了一个warning警告

那么这个程序是一定无法运行了吗?

答案是不一定,取决于是否清理函数栈帧

如果count函数已经被销毁,那么这时ret指向的位置将为一个随机值(这里可以类比c语言当中的野指针),如果count函数没有被销毁,原本的n开辟的空间还存在,那么此时ret指向的位置就是n的位置。

那么我们再来看一个代码,可能又会让老铁们摸不着头脑了~~~

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1,2);
    cout << "ret= " << ret << endl;
    Add(3, 4);
    cout << "ret= " << ret << endl;
    return 0;
}

咦?第二次并没有将Add中的c赋值给ret,为什么ret的值也发生改变了呢?

这里我们还是通过图像进行理解~~

引用和指针的区别

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

但是在底层的实现上实际上是有空间的,因为引用是按照指针的方式来实现的。

引用和指针的不同点

1.引用概念上定义一个变量的别名,指针存吃一个变量地址。

2.引用在定义上必须初始化,指针则没有具体的要求。

3.引用在初始化时引用一个实体之后,就不能再引用其他实体,而指针可以在任何的时候指向任何一个同类型的实体。

4.没有NULL引用,但是有NULL指针。

5.在sizeof中的含义不同引用结果为引用类型的大小,但是指针始终是地址空间所占字节数

6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7.有多级指针,但是没有多级引用。

8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理。

9.引用比指针使用起来相对更加安全。

内联函数

在我们c语言的学习过程中,我们学过一个概念,叫做宏替换,大家还记得宏替换的相关知识点吗?宏替换需要注意的坑有很多,不好控制。比如,我们写一个Add(x,y)的宏替换,应该怎么写呢?可能

概念

inline修饰的函数叫做内联函数编译时c++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内敛函数提升程序运行的效率。

(这里简要介绍以下,call的含义其实也就可以理解为是为函数开辟了新的栈帧,所以ADD括号内的就是开辟的新的函数站真的地址)

我们发现,这里在调用内联函数inline时,并没有开辟新的函数栈帧,而是进行了函数的展开,证明了inline的作用。

特性

1、要注意,inline是一种用空间换时间的做法,如果编译器将函数当成内联函数处理,那么在编译阶段会用函数体替换函数调用缺陷,可能会是目标文件变大;优势,减少了调用函数的开销,提高程序的运行效率。

2、inline对于编译器而言只是一个建议不同的编译器关于inline实现机制可能不同,一般建议:函数规模较小,不是递归或者频繁调用的函数,采用inline进行修饰。

3、inline不建议声明和定义分离,分离会导致连接错误。因为inline被展开,就没有函数地址了,链接就会找不到。(也就是说如果inline声明和定义分离,那么就只能在当前文件进行使用)

auto关键字(c++11)

概念

auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器时期推导而得。(简单来说,就是auto可以自动推导变量的类型是什么,不用再像c语言当中,需要对变量进行类型的表述)

int TestAuto()
{
    return 10;
}

int main()
{
    int a = 10;
    auto b = a;
    auto c = 'a';
    auto d = TestAuto();

    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;


}

注意

**使用auto定义变量时,必须对其进行初始化,否则在编译阶段编译器无法根据初始化表达式推导auto的实际类型。**因此,auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期间会将auto替换为变量的实际类型。

auto的使用细则

1.auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时必须加&

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;
	auto& c = x;

	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;

	return 0;
}

2.在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

void TestAuto()
{
	auto a = 1, b = 2;
	auto c = 3, d = 4.0;
}

auto不能推导的场景

1.auto不能作为函数的参数,也不能作为返回值

2.auto不能直接用来声明数组

基于范围的for循环

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会犯错误。因此c++引入了基于范围的for循环for循环后的括号由冒号“:”,分为两个部分:第一个部分是范围内用于迭代的变量,第二个部分则表示被迭代的范围。

void TestFor()
{
	int a[] = { 1,2,3,4,5 };
	for (auto& e : a)
	{
		e *= 2;
	}
	for (auto e : a)
	{
		cout << e << " ";
	}
	return;
}
int main()
{
	TestFor();
}

指针空值nullptr

我们在c语言中,用NULL表示空指针,但在c++当中,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量,所以,在c++中,我们引入了新的符号nullptr。所以,为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

总结

我们今天从命名空间、c++的输入输出、缺省函数、函数重载、引用、内联函数、auto关键字、基于范围的for循环、空指针nullptr这几个大的方面,详细阐述了C语言与c++的区别,帮助老铁们实现c语言到c++的顺利过渡,不知道老铁们学废了嘛~~

学习编程语言,练习是必不可少的,基础知识只是最基本的东西,我与诸君共勉,不仅要认真总结基础知识点,更要重视实操,不然也只是徒劳无功~~~~

本人水平有限,难免出现知识点遗漏、错误的情况,愿各位大佬批评指正,不胜欣喜。都看到这里了,不点赞、评论、收藏鼓励三连再走嘛,感谢您的观看!!!

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值