ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【C++初阶】速过C++语法基础

【C++初阶】速过C++语法基础

文章目录

  • (一)命名空间 namespace
    • (1)namespace的价值
    • (2)namespace的定义
      • ①namespace的形式:
      • ②namespace的本质
        • -C++中的域相关
    • (3)namespace的使用
      • 访问namespace内的内容 ::
      • PS:
        • -头文件和命名空间的展开各自代表什么含义?
  • (二)输入输出
    • (1)输出
    • (2)输入
    • 代码
  • (三)缺省参数
    • (1)全缺省
    • (2)半缺省
  • (四)函数重载
      • 代码:
      • 注意:
      • -偶然发现的编译、链接报错小知识
  • (五)引用
    • (1)定义
    • (2)写法
    • (3)特性
      • 有没有觉得最后两点莫名矛盾?
      • 例题分析
    • (4)使用
    • (5)const引用(常引用)
      • 注意:
      • 指针和引用的区别
  • (六)inline 和 nullptr
    • (1)inline
      • 内联函数的写法
    • (2)nullptr(C++11后才添加)
      • 注:void*和任意类型的指针之间的转换

(一)命名空间 namespace

(1)namespace的价值

一言以蔽之:用于避免命名冲突

原因:同一域中不能定义同名变量。

(2)namespace的定义

①namespace的形式:

1.写在全局处namespace+域名+{ 可以定义变量、函数和类型等(定义/声明) }

namespaceyuan{//定义rand变量intrand=10//定义自定义类型student结构体structstudent{intname;doubleweight;}//定义函数intAdd(intx,inty){returnx+y;}}intmain(){inta=yuan::rand;structyuan::studentp=NULL;printf("%d",Add(1,2));}

不能写在namespace里所有可执行语句(函数调用、循环分支、表达式、赋值语句……) 一旦写了,编译器直接报错;

2.namespace只能定义在全局处,可以嵌套定义;
3.项目工程中多文件的同名namespace会被认为是同一个namespace,不会冲突(逻辑上的合并);
4.C++标准库放在一个名为std(standard)的命名空间里。

②namespace的本质

namespace的本质是定义出一个域,这个域和全局域各自独立。由于不同的域里是可以定义同名变量的,所以命名冲突就可以被解决了。

-C++中的域相关

C++中有局部域、全局域、命名空间域和类域。
域影响编译时语法查找一个变量/函数/类型出处(声明/定义)的逻辑。
全局域和局部域除了会影响查找出处逻辑,还会影响变量的生命周期;命名空间域和类域不影响变量的生命周期。
命名空间内的内容属于全局变量,生命周期是全局的,只是加了一层限制罢了。

(3)namespace的使用

访问namespace内的内容 ::

①指定命名空间单个访问

namespaceyuan{//定义rand变量intrand=10//定义自定义类型student结构体structstudent{intname;doubleweight;}//定义函数intAdd(intx,inty){returnx+y;}}intmain(){//指定命名空间单个访问intr=yuan::rand;//命名空间yuan + 通配符:: + 命名空间内的变量randsturct yuan::student p=NULL;//命名空间中的结构体访问,命名空间yuan和通配符::写在struct和标签名之间printf("%d",Add(1,2));}

PS:

a.如果只使用通配符::,默认访问全局同名变量。(命名空间之外)

b.在嵌套定义中,想访问最里层命名空间的某个内容,就需要逐层使用: :;

#include<stdio.h>namespaceyuan{inta=0;namespaceserverus{intb=10;}}inta=20;intmain(){inta=10;intb=30;printf("%d\n",yuan::serverus::b);}

②using将命名空间内的某个成员展开

#include<stdio.h>namespaceyuan{inta=0;namespaceserverus{intb=10;}}usingyuan::serverus::b;//将yuan命名空间中的serverus命名空间中的b成员展开inta=20;intmain(){inta=10;//int b = 30;//printf("%d\n", yuan::serverus::b);printf("%d",b);//这样就可以直接访问到yuan命名空间中的serverus命名空间中的b了}

③展开命名空间内的所有成员(日常练习可以使用,但实际的项目开发不建议用

#include<stdio.h>namespaceyuan{inta=0;namespaceserverus{intb=10;}}usingnamespaceyuan;//展开yuan命名空间usingnamespaceyuan::serverus;//展开yuan中的serverus命名空间//格式:using namespace + 命名空间名intmain(){//int a = 10;//int b = 30;printf("%d\n",a);printf("%d\n",b);}
-头文件和命名空间的展开各自代表什么含义?

头文件展开:#include 是直接把整个头文件的全部文本,原封不动复制粘贴到当前.c文件里,不管是声明、宏、结构体、注释,全部照搬,不会自动筛选只复制函数声明,更不会复制函数声明对应的函数实现。
(由于初学C++,有些地方习惯先从C的角度理解)

命名空间的展开8:可以理解成打通了一条通向命名空间内部的道路,让编译器找东西时,可以进入命名空间的房间里搜查。

(二)输入输出

【由于作者初学C++,故而只摆概念,不解释。】

< iostream>:是Input Output Stream的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象(我目前理解它就是一个C++输入输出函数需要包含的头文件,放在std命名空间里)。
std::cin:是istream类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输⼊流。
std::cout:是ostream类的对象,它主要⾯向窄字符的标准输出流。
std::endl:是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区(换行作用,类似于C语言中的\n转义字符)。

(1)输出

① <<是流插⼊运算符,>>是流提取运算符。
(C语⾔还⽤这两个运算符做位运算左移/右移)

②使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输⼊输出可以⾃动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出。

(2)输入

注意:输入用>> , 输出用<< ,不要用反了!

代码

//输入输出#include<iostream>usingnamespacestd;intmain(){inta=10;doubleb=2.5;charc='x';cout<<a<<' '<<b<<' '<<c<<endl;cout<<a<<' '<<b<<' '<<c<<"\n";cout<<a<<' '<<b<<' '<<c<<'\n';cin>>a>>b>>c;cout<<a<<' '<<b<<' '<<c;return0;}

(三)缺省参数


补充解释一下最后一点:如果只有函数定义,在函数定义上写缺省值是合法的,但是如果同时有函数定义和函数声明,那么就规定只能在声明处给缺省值。

文字描述说了也是一知半解,看看具体代码实现:

(1)全缺省

全缺省:调用函数不传任何实参,形参的值全部采用缺省值。

//全缺省#include<iostream>usingnamespacestd;//函数声明intAdd(intx=5,inty=10);intmain(){//函数调用intret=Add();cout<<ret<<endl;return0;}//函数定义intAdd(intx,inty){returnx+y;}


在这里也能看到,想给缺省值,是放到函数声明里的。

(2)半缺省

半缺省:只能从右往左给缺省值,函数调用是实参只能从左往右给。
(理解实参拷贝的顺序,就能明白为啥函数声明里的缺省值是从右往左的了,两者要互补。)

#include<iostream>usingnamespacestd;//半缺省//函数声明//只能从右往左给缺省值intAdd(intx,inty=20);intmain(){intret=Add(2);cout<<ret<<endl;return0;}//函数定义intAdd(intx,inty){returnx+y;}



(四)函数重载

C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数、类型或者顺序不同,对返回值类型和函数体无要求。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。

好处:C++中有了函数重载,就不需要写一堆逻辑一样但形参不同的函数了。

代码:

返回值无所谓 函数名必须相同 (形参个数、类型或者顺序必须不同)

//函数定义doubleAdd(intx,doubley){returnx+y;}//形参类型不同intAdd(intx,inty){returnx+y;}//形参个数不同intAdd(intx,doubley,intz){returnx+y+z;}//形参顺序不同doubleAdd(doubley,intx)//double Add(double x, int y) 同样可以{returnx+y;}

注意:

①下面三个函数定义不构成重载,它们是完全一样的。

当函数交换顺序的形参是同一类型或者只是改变了形参的名字,这两种情况下,不会构成重载,编译会报错。

intAdd(intx,inty){returnx+y;}intAdd(inty,intx){returnx+y;}intAdd(inta,intb){returna+b;}


②放在不同域内的同名函数不是函数重载。

#include<iostream>usingnamespacestd;//形参类型不同intAdd(intx,inty){returnx+y;}namespaceyuan{intAdd(intx,inty){returnx+y;}intAdd(intx,doubley){returnx+y;}}intmain(){return0;}

一个是全局域一个是命名空间域,当然不会构成重载,这种编译不报错。


-偶然发现的编译、链接报错小知识

在VS中,编译报错是以C开头的,而链接报错是以LNK开头的。



(五)引用

(1)定义

(2)写法

类型&引用别名=引用对象;
#include<iostream>usingnamespacestd;intmain(){inta=9;//b是a变量的别名,b不开空间,b代表a也就是9int&b=a;//c又是b的别名,其实也就是a的别名,依旧不开空间,c也代表a也就是9//可以给别名取别名int&c=b;cout<<a<<endl;cout<<b<<endl;cout<<&a<<endl;cout<<&b<<endl;return0;}

(3)特性

  • 引用在定义时必须初始化;
  • 一个变量可以有多个引用;
  • 别名发生改变了,就是变量发生改变了;
  • 引用一旦引用了一个实体,就不能再引用其他实体。

有没有觉得最后两点莫名矛盾?

—— 既然都不能引用别的实体,有何谈改变呢?

但其实不矛盾。
不能改变一个已经引用了实体的引用去引用其他实体,但是可以改变引用自身的值。简单说就是如果a=10,b是a的引用,那么b可以改变值为20、30,但是b不能成为c的引用。

例题分析

#include<iostream>usingnamespacestd;intmain(){inta=10;int&b=a;intc=20;b=c;//这是让b引用c,还是将c的值赋给b?cout<<b<<endl;cout<<&a<<" "<<&b<<endl;cout<<&c<<" "<<&b<<endl;return0;}


将c的值赋给b,b=20,由于b是a的别名,和它共用同一块存储空间,a的值也会变成20。打印a、b地址时,自然也是一样的地址。


(4)使用

(5)const引用(常引用)

使用指针和引用是间接访问变量,相当于开辟出一条额外的通道,而这条通道的权限是有限制的,它可以缩小但是不能放大,即通过指针、引用访问对象的权限可以不变、缩小,但是不能放大。

#include<iostream>usingnamespacestd;intmain(){//情况一:讲明权限放大的报错情况constinta=10;// a不能被修改int&b=a;//会报错,此时b是a的别名,如果通过b是可以改变a的值的//这说明对象的访问权限放大了constint&rb=a;//不会报错//使用常引用,rb也不能修改,则a访问权限并未发生变化//情况二: 讲明权限缩小的合法情况intc=20;//c变量可以修改constint&rc=c;//不会报错,rc引用不能修改值,也就是不能修改c的值,权限缩小了,但是是合法的//注意: 引用的访问权限不会改变原变量的访问权限rc++;//报错c++;//不报错//情况三:讲明C++中规定临时对象具有常性,不能被修改//临时对象:数值字面量、返回值、表达式的值、类型转换后的值(无论显隐)//数值字面量:int&d=10;//会报错//10是一个临时对象,具有常性,不可修改;生命周期在本语句结束后结束//使用普通引用,一会放大访问权限,二是在本条语句结束后临时对象的空间被销毁了,可它的别名却还存在,会造成“悬空引用”(野引用)//普通引用无法延长临时对象的生命周期,但是常引用可以延长临时对象的生命周期,那常引用什么时候销毁,临时对象就什么时候销毁constint&rd=10;//表达式的值:inte=10;intf=20;int&g=e+f;//会报错,表达式的值会放在一个临时对象里,临时对象具有常性,应该用常引用constint&rg=e+f;//类型转换后的值:inth=10;double&i=h;//会报错,i引用的不是h,而是类型转换后放在临时对象里的值//而临时对象不能修改,它的引用要是常引用constdouble&ri=h;return0;}

注意:

①讲明C++中规定临时对象具有常性,不能被修改。
(临时对象:常量数字、返回值、表达式的值、类型转换后的值)
②普通引用无法延长临时对象的生命周期,但是常引用可以延长临时对象的生命周期,那常引用什么时候销毁,临时对象就什么时候销毁。

const引用的价值不止以上几条,在一些需要用引用接收参数的形参上,也可以使用const,这样就可以接收更多类型的值(常量数字、表达式的值、类型转换后的值、函数返回值…),如果是普通引用就不能接收这些放在临时变量里具有常性的值了(因为这是权限放大,不合法)。

指针和引用的区别


(六)inline 和 nullptr

(1)inline



也就是说针对一些频繁调用的短小函数,可以使用内联,编译器也会听你的,但是如果是一些复杂的函数,即使加上内联,编译器也可能不会听你的。

内联函数的写法

直接在普通函数的返回值前加一个inline就好。

inlineintADD(inta,intb){returna+b;}

(2)nullptr(C++11后才添加)

NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:

#ifndefNULL#ifdef__cplusplus#defineNULL0#else#defineNULL((void*)0)#endif#endif

可以看到,在C++中,NULL宏定义为0,那么在如下的函数调用时,就会存在问题:

#include<iostream>usingnamespacestd;voidf(intx){cout<<"f(int x)"<<endl;}voidf(int*ptr){cout<<"f(int* ptr)"<<endl;}intmain(){f(0);//调用f(int x)函数f(NULL);// 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((int*)NULL);//成功调用f(int*)函数f((void*)NULL);// 编译报错// void*既不适合int参数,又不适合int*参数——注f(nullptr);//成功调用f(int*)函数return0;}

C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。

注:void*和任意类型的指针之间的转换

在C语言中,void类型的指针和任意类型的指针之间的转换都支持隐式转换,不写强转,编译器也不会报错,但是在C++中,void类型的指针和任意类型的指针之间的转换不支持隐式转换,编译时会报错。除非显示强制类型转换。


初学C++,如有错误,欢迎指出!

——end——

返回列表