【C++】-- C++入门

发布于:2023-01-04 ⋅ 阅读:(436) ⋅ 点赞:(0)

目录

1、命名空间(用于大型程序的工具)

命名空间定义

命名空间自成一个作用域

命名空间的使用

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

使用 using 将命名空间中成员引入

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

2、C++输入&输出

3、缺省参数

缺省参数概念

缺省参数分类

全缺省参数

半缺省参数

     同时有缺省参数:

      定义有缺省参数声明没有缺省参数:

4、函数重载

函数重载概念

参数列表的类型不同

参数列表的类型顺序不同

参数列表的类型个数不同

5、引用(左值引用)

引用特性

使用场景

做参数

做返回值

常引用

引用和指针的区别

引用和指针的不同点:

6、内联函数

内联函数概念

内联函数特性

inline不建议声明和定义分离,分离会导致链接错误。

7、auto关键字(C++11)

auto不能再同一行定义多个不同变量

auto不能作为函数的参数

auto不能直接用来声明数组

8、基于范围的for循环(C++11)

9、指针空值---nullptr(C++11)


1、命名空间(用于大型程序的工具)

        变量、函数和类(C++针对于C语言的结构体,所提出的优化)的名称将都存在于全局作用域中,可能会导致很多冲突。(在实际的项目中,往往会使用多个独立开发的库,这些库又会定义大量的全局名字,就会难免出现同名的情况)导致命名空间污染(namespace pollution),使用命名空间(namespace关键字)的目的是对标识符的名称进行本地化。

命名空间定义

// 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
// 不能定义在函数或类的内部
// 1. 普通的命名空间
namespace name // name为命名空间的名称
{
        // 命名空间中的内容,既可以定义变量,也可以定义函数
        int a ;
        int Add ( int left , int right)
        {
                return left + right ;
        }
// 命名空间作用域后面无需加分号
// 2. 命名空间可以嵌套
namespace name_1 // name_1为命名空间的名称
{
        int a ;
        int b ;
        int Add ( int left , int right )
        {
                return left + right ;
        }
        namespace name_2  // name_2为命名空间的名称
        {
                int c ;
                int d ;
                int Sub ( int left , int right )
                {
                        return left - right ;
                }
        }
} // 命名空间作用域后面无需加分号

        和其他名字一样,命名空间的名字也必须在定义它的作用域内并需要保持唯一性。

命名空间自成一个作用域

        和其他作用域相同,每一个名称都只能出现一个唯一实体,否则即是命名冲突。不同的命名空间即作用域不同,所以在不同名的命名空间里可以有相同名字的成员。

        定义在命名空间里的名字可以被该命名空间内的成员直接访问,而位于该命名空间外的访问必须需要使用 ::(域操作符)进行明确指出所使用。

命名空间的使用

        由于命名空间自成一个作用域:

// 定义一个命名空间N
namespace N
{
        int a = 10 ;
        int b = 20 ;
        int Add ( int left , int right )
        {
                return left + right ;
        }
        int Sub ( int left , int right )
        {
                return left - right ;
        }
}
int main ()
{
        printf ( "%d\n" , a ); // 该语句编译出错,无法识别a
        return 0 ;
}

 (VS2019实现)   

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

int main()
{
    printf("%d\n", N::a);
    // std::cout << N::a << std::endl;
    return 0; 
}
  • 使用 using 将命名空间中成员引入

        经常使用于,指定名空间访问+展开常用。

using N::a;
using std::cout;
using std::endl;
int main()
{
     printf("%d\n", a);
     // cout << a << endl;
     return 0; 
}
  • 使用 using namespace 将命名空间名称引入

        日常书写我们都会在文件中写上,using namespace std; 其就是对 std(C++标准库)进行引用,使用其中的函数或者对象。

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

Node:

     using namespace std; 相当于直接将其空间空间内的定义在全局。

  1. 项目中,尽量不要using namespace std;

  2. 日常练习用using namespace std;

  3. 项目,指定名空间访问+展开常用

2、C++输入&输出

#include<iostream>
using namespace std;
int main()
{
    int i;
    double d;
    cin >> i >> d; //无需指定类型,可以自动识别类型
    // << 流提取运算符

    cout << "Hello world!!!" << endl; //endl 等价于 '\n'
    // << 流插入运算符
    return 0; 
}

Note:

  1. 使用cout标准输出(控制台)cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间(早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此使用<iostream> + std的方式。)
  2. 使用C++输入输出更方便,不需增加数据格式控制,比如:针对C语言的printf函数整形--需%d,字符--需%c。

不需增加数据格式控制的缺陷:

        当我们希望打出这样的字符串的地址时。


3、缺省参数

        给C++中函数的参数配备胎。

缺省参数概念

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

Note:缺省值必须是常量或者全局变量

#include <iostream>
using namespace std;
//  int a = 0就是缺省参数
void Func(int a = 0)
{
	cout << a << endl;
}
int main()
{
	Func(); // 没有传参时,使用参数的默认值
	Func(10); // 传参时,使用指定的实参
}

缺省参数分类

  • 全缺省参数

#include <iostream>
using namespace std;
void Func(int a = 0, int b = 0, int c = 0)
{
	cout << a << ' ' << b << ' ' << c << endl;
}
int main()
{
	Func(); // 没有传参时,使用参数的默认值
	Func(10);
	Func(10, 20); //传参是从左往右给(对于Func:先a 再b 然后c)
	Func(10, 20, 30);
}

Note:C++规定,不允许跨越传递

  • 半缺省参数

       缺省部分参数。

#include <iostream>
using namespace std;
void Func(int a, int b = 0, int c = 0)	//必须从右往左连续缺省,不能间隔
{
	cout << a << ' ' << b << ' ' << c << endl;
}
int main()
{
	Func(10); // 有几个未缺省,就必须传几个
	Func(10, 20); //传参是从左往右给(对于Func:先a 再b 然后c)
	Func(10, 20, 30);
}

Note:对于函数半缺省必须从右往左连续缺省,不能间隔

Note:缺省参数不能在函数声明和定义中同时出现

//test.h(头文件)
void Func ( int a = 10 );
// test.cpp
void Func ( int a = 20 )
{ }
// 如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
// 声明与定义分离,那么缺省参数只能在声明中出现。 
  •      同时有缺省参数:

  (VS2019实现)  

  •       定义有缺省参数声明没有缺省参数:

 (VS2019实现)   


4、函数重载

        在C语言中是不允许同名函数的存在的。重载函数(overloaded function)是C++支持的一种特殊函数,C++编译器对函数重载的判断更是C++语言中极为复杂的内容之一。(一个函数有多个意义)

函数重载概念

         函数重载 (必须在同一个域里面才构成) 是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表 (参数个数、类型、类型顺序)必须不同 ,常用来处理实现功能类似数据类型不同的问题。
  • 参数列表的类型不同

#include<iostream>
using namespace std;
//参数列表的类型不同
int Add(int e1, int e2)
{
	return e1 + e2;
}
double Add(double e1, double e2)
{
	return e1 + e2;
}
  • 参数列表的类型顺序不同

#include<iostream>
using namespace std;
//参数列表的类型顺序不同
int Add(double e1, int e2)
{
	return e1 + e2;
}
double Add(int e1, double e2)
{
	return e1 + e2;
}
  • 参数列表的类型个数不同

#include<iostream>
using namespace std;
//参数列表的类型个数不同
int Add(int e1, int e2)
{
	return e1 + e2;
}
double Add(int e1, int e2, int e3)
{
	return e1 + e2;
}

Note:返回值不同不构成重载。(调用时无法进行区分)


5、引用(左值引用)

        引用(reference)为对象起了另外一个名字,声明类型引用(refers for)另外一种类型。通过将声明符写成 &变量名 的形式来定义引用类型。

Note:引用不是新定义一个对象,而是给已存在变量取了一个别名

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

int val = 1024;

int& refval = val; // refval指向val(是val的另一个名)

引用特性

int main()
{
        // 1、引用在定义是必须初始化
        int a = 10;

        // 2、一个变量可以有多个引用
        int& b = a;
        int& c = a;
        int& d = c;

        // 3、引用一旦引用一个实体,再不能引用其他实体
        int x = 20;
        b = x;  // b不是x的别名,而是将x赋值给b
        return 0;
}

使用场景

  • 做参数

//做参数 -- 1、输出型参数 2、大对象传参,提高效率
void Swap(int& e1, int& e2) 
{
    int tem = e1;
    e1 = e2;
    e2 = tem; 
}
  • 做返回值

// 返回返回对象的别名 -- 1、输出型返回对象,调用者可以修改返回对象 2、减少拷贝,提高效率
int& Count()
{
    static int n = 0;
    n++;

    // ...

    return n; 
}

常引用

        主要需要注意的是权限问题(权限的放大缩小只针对与引用与指针)

int main()
{
	// 权限不能放大
	const int c = 20;
	int& d = c; // 错误

	// 权限可以缩小
	int e = 30;
	const int& f = e; // 正确

	int ii = 1;
	double& edd = ii; // 错误
	// 隐式转换会生成临时变量,而临时变量具有常属性,所以也是权限的放大。
}

引用和指针的区别

  • 语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间;指针开了4或8字节。
  • 底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

 (VS2019实现)   

引用和指针的不同点:

  1. 引用在定义时必须初始化,指针没有要求。
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
  3. 没有NULL引用,但有NULL指针。
  4. sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
  6. 有多级指针,但是没有多级引用。
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
  8. 引用比指针使用起来相对更安全。(指针更强大,更危险,更复杂;引用相对局限一些,更安全,更简单)

6、内联函数

        以inline修饰的函数叫做内联函数

内联函数概念

        在编译中,时常会出现仅仅几行代码,却大量引用的问题,尤其是Swap交换函数。这导致多次的压栈与多次开辟栈帧,会出现大量的时间耗费。

//数据交换函数
void Swap(int e1, int e2)
{
    int tmp = e1;
    e1 = e2;
    e2 = tmp;
} 

对此的优化:

       C语言对此提供了宏函数,但是由于宏:1,可读性差 2,没有类型安全检查 3,不方便调试。所以C++提出了内联函数,内联函数是一种以空间换时间的做法。

内联函数特性

        编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。如果定义为inline的函数体内有函数内部实现代码指令长度比较长递归(会让编译出来的程序冗余)等等编译器优化时会忽略掉内联

//数据交换内联函数
inline void Swap(int e1, int e2)  //inline 符合条件的情况下,在调用的地方展开
{
    int tmp = e1;
    e1 = e2;
    e2 = tmp;
} 
  • inline不建议声明和定义分离,分离会导致链接错误。

        因为inline被展开,就没有函数地址了,链接就会找不到。

// test.h
#include <iostream>
using namespace std ;
inline void fun ( int i );
-----------------------------------------------------
// test.cpp
#include "F.h"
void fun ( int number )
{
        cout << number   << endl ;
}
-----------------------------------------------------
// main.cpp
#include "F.h"
int main ()
{
        fun ( 10 );
        return 0 ;
}

 (VS2019实现)   

当程序开始运行:

  1. main.cpp:main运行,发现fun函数的调用,虽然,有对于其是内联函数的声明(test.h),但是没有内联函数的实现,所以只能call其地址。

  2. test.cpp:有对于其是内联函数的声明(test.h),而内联函数不需要放入符号表(因为fun函数本身也不长,编译器会把inline函数就地展开,于是认为在调用的地方已经展开了)。

  3. 运行的时候:mian中的fun函数的调用想展开,但是没有函数的实现只有声明,所以只能选择call,然后,就导致连接不上。

所以,建议内联声明与定义不要分离。


7、auto关键字(C++11)

        编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型。然而这并非十分容易,在后面针对于复杂的类型,十分容易错误。而auto是让编译器去替我们去分析表达式所属的类型。

类型比较长的时候,auto自动推导:

#include <string>
#include <map>
int main ()
{
        std::map < std::string , std::string > m {{ "apple" , " 苹果 " },{ "orange" , " 橙子 "  }{ "pear" , " " }};
        std::map < std::string , std::string > :: iterator it = m . begin ();
        // auto it = m.begin();
        //....
        return 0 ;
}
double fun()
{
	return 10.10;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'c';
	auto d = fun();
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

 (VS2019实现)   

int main()
{
	int a = 10;
	auto b = &a;	//int
	auto* c = &a;	 //int*  *强调一定要传指针
	auto& d = a;	//int  &强调一定是引用
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

  (VS2019实现)   

  • auto不能再同一行定义多个不同变量

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

void TestAuto ()
{
        auto a = 1 , b = 2 // 可以
        auto c = 3 , d = 4.0 ;   // 该行代码会编译失败,因为 c d 的初始化表达式类型不同
}
  • auto不能作为函数的参数

// 此处代码编译失败,程序执行的时候,需要对此函数生成指令,生成指令就要建立栈帧,参数是在这个栈帧里面的。然而,根本不知道它的类型,所以,不知道要开多大的栈帧。
void TestAuto ( auto a )
{ }
  • auto不能直接用来声明数组

//  数组的初始化是在运行期间完成的,而 auto 是在编译期间就完成 的 (先编译后运行)。 数组不能具有其中包含“ auto ” 的元素类型,即不能出现“ auto[] ”。
void TestAuto ()
{
        int a [] = { 1 2 3 };
        auto b [] = { 456 };
}

8、基于范围的for循环(C++11)

        C++11新标准引入一种更简单的 for 语句,这种语句可以遍历容器或其他序列的所有元素。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

void Test_For()
{
    int array[] = { 1, 2, 3, 4, 5 };

    for (auto e : array)    //变化前
        cout << e << " ";
    cout << endl;

    for (auto& e : array)   //使用应用使得可以更改数据
        e *= 2;

    for (auto e : array)    //变化后
        cout << e << " ";
    cout << endl;
}

         范围for自动推导类型,自动取array中的值赋值给e,自动迭代(自动++),自动的判断结束。

 (VS2019实现)  

Note:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环


9、指针空值---nullptr(C++11)

        在之前的C++98中的指针空值中,我们所使用的空指针的定义是NULL。

         所以,相对应的以宏将NULL定义为0,就会出现一下类似的情况等:

using namespace std;
void pointer(int)
{
	cout << "pointer(int)" << endl;
}

void pointer(int*)
{
	cout << "pointer(int*)" << endl;
}

int main()
{
	int* p = NULL;
	pointer(0);
	pointer(NULL);
	pointer(p);
	return 0;
}

 (VS2019实现)  

        pointer(NULL)传递的是int类型的。

#include <iostream>
using namespace std;
void pointer(int)
{
	cout << "pointer(int)" << endl;
}

void pointer(int*)
{
	cout << "pointer(int*)" << endl;
}

int main()
{
	pointer(nullptr);
	return 0;
}

 (VS2019实现)  

        pointer(nullptr)传递的是int*类型的。

Note:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的
  2. C++11中,sizeof(nullptr) sizeof((void*)0)所占的字节数相同。 (可以理解为nullptr在底层就等价于0。然后,强转成了(void*)0)
本文含有隐藏内容,请 开通VIP 后查看