【C++】C++基础知识

简介: 其中 { } 内容即为命名空间的成员,注意最后右花括号后不用加分号结尾。

一. 命名空间


1. 命名空间的定义


namespace + 命名空间的名字 + {}。其中 { } 内容即为命名空间的成员,注意最后右花括号后不用加分号结尾。


namespace 是 C++ 的一个关键字。

命名空间中的内容,既可以定义变量,也可以定义函数。

命名空间可以嵌套。

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

//定义命名空间N1
namespace N1
{
  int a = 10;
  int b = 20;
  int Add(int left, int right)
  {
  return left + right;
  }
  // 嵌套在N1中的命名空间N2
  namespace N2
  {
  int a = 30;
  int d = 40;
  int Sub(int left, int right)
  {
    return left - right;
  }
  }
}



2. 命名空间使用


一个命名空间就定义了一个新的归属,命名空间中的所有内容都局包含于该命名空间中。内部可以直接使用,外部使用时必须指明是哪个命名空间。


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


int main()
{
  printf("%d\n", N1::a);     // 10
  printf("%d\n", N1::N2::a); // 30
  return 0;
}


方法二:使用 using 将命名空间中的成员引入


using N1::a;
int main()
{
  // a被引入了可以直接使用
  printf("%d\n", a); // 10
  return 0;
}


方法三:使用 using namespace 将整个命名空间引入

using namespce N1;
int main()
{
  // 命名空间 N1 中的a和b都可以直接使用了
  printf("%d\n", a); // 10
  printf("%d\n", b); // 20
  return 0;
}


3. 说明


工程项目中:可能会产生命名冲突,所以把常用库里面一些对象或者类型展出来。比如:using std::cin、using std::cout 等。


日常练习中:不在乎跟库命名冲突,所以可以把库的命名空间全部展开。比如:using namespace std;


二. C++输入和输出


cin : 标准输入(键盘)

cout : 标准输出(控制台)


必须包含 < iostream > 头文件和展开 std 命名空间。

使用C++输入输出不需像 C 一样进行数据格式控制,比如:整形 - %d,字符 - %c

#include<iostream>
using namespace std;
int main()
{
  int a;
  // 输入
  cin >> a;
  // 输出
  cout << a;
  return 0;
}


20210523095007797.png

三. 缺省参数】


1. 概念


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


// TestFunc 函数有一个缺省参数
void TestFunc(int a = 0)
{
  cout<<a<<endl;
}
int main()
{
  // 没有传参时,使用参数的缺省值
  TestFunc();   
  // 传参时,使用指定的实参
  TestFunc(10); 
}

20210523095237822.png


2. 缺省参数分类


1、全缺省参数(形参全部设置为缺省)


void TestFunc(int a = 10, int b = 20, int c = 30)
{
  cout<<"a = "<<a<<endl;
  cout<<"b = "<<b<<endl;
  cout<<"c = "<<c<<endl;
}


2、半缺省参数(形参部分缺省,必须从右往左缺省,并且要连续)


void TestFunc(int a, int b, int c = 30)
{
  cout<<"a = "<<a<<endl;
  cout<<"b = "<<b<<endl;
  cout<<"c = "<<c<<endl;
}


3. 缺省参数的几点说明


半缺省参数必须从右往左连续给出,不能间隔着给。

缺省参数不能在函数声明和定义中同时出现,如果声明和定义分离的话,建议在声明那里缺省,便于在头文件里查找修改。

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

C语言不支持缺省参数(编译器不支持)。


四. 函数重载


1. 概念


C++允许定义函数名相同,实参(类型,顺序,个数)不同的多个函数,常用来实现功能类似但数据类型不同的问题。


2. C++支持函数重载的原因


在 Linux 中:经 gcc 修饰后的函数其名字不变,而经 g++ 修饰后的函数,其名字变成 _Z + 函数名长度 + 函数名 + 参数类型首字母,即 g++ 编译器将函数参数类型的信息也给添加到了修饰后的函数名中。


综上分析,C语言之所以没办法支持函数重载,因为同名函数没办法被区分开。而 C++ 中只要是参数类型不同,即使是同一个函数,它被修饰后的名字就不一样,这样就实现了函数重载。



20210410201508380.png

ada2e80e2f704648b7eb92032a2ea64c.png


3. 函数重载的几点说明


① 函数是否构成重载与形参变量的名字相同与否无关,只和形参的类型有关:


// 不构成重载,因为函数名经过编译器修饰后还是一样的
void func(int i, int j) //_Z4funcii
{}
void func(int a, int b) //_Z4funcii
{}


② 函数是否构成重载与返回值类型无关:


// 返回值类型都为 int
// 下面两个函数不构成函数重载
int Add(int i, int j)    // _Z3Addii
{}
// 返回值类型为double
double Add(int i, int j) // _Z3Addii
{}


③ 涉及到缺省参数时可能会发生调用不明确的问题:


void func(int a)
{}
void func(int a,int b=10)
{}
int main()
{
  // error:不明确到底是调用带缺省的还是不带缺省的
  func(10);
}


3. extern “C”


C++ 可以使用自己的那套函数名修饰规则来支持函数重载。因为 C++ 兼容 C语言,所以它也可以支持 C语言 的那套函数名修饰规则(就是直接以函数名来区分函数);但反过来 C语言 不支持 C++ 的那套函数名修饰规则。


场景:当我们用 C++ 写出几个函数接口卖给客户时,为了满足更多客户的需求(要求不仅 C++ 能调用,C语言 也要能用),我们干脆统一用 C语言 的那套函数名修饰规则来处理这个函数接口,在函数声明处加上 extern “C” 即可。


比如:tcmalloc 是 google 用 C++ 实现的一个项目。他提供 tcmallc() 和 tcfree() 两个接口。但如果是 .C 项目就没办法使用这套接口,可以在函数声明前加上 extern “C” 来解决。20210523103430782.png



五. 引用


1. 概念


给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和被引用的变量共用同一块内存空间。


使用规则: 类型& 引用变量名 = 引用实体;


int main()
{
  int a = 10;
  int& ra = a;// ra 引用 a
  //a和ra地址相同
  printf("%p\n", &a);
  printf("%p\n", &ra);
  return 0;
}

991809c1e09848f5aa9522ae613db182.png

2. 引用的几点说明


引用变量在定义时必须初始化,即必须有引用实体。

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

引用对象一旦引用了一个实体,就不能再去引用其他实体。

4b845ad4ad5547e0a856018f1ed42930.png

3. 常引用


在引用取别名和传指针变量的值时,访问的权限可以缩小,不能放大

int main()
{
  // 1.该语句编译时会出错,原变量a被const修饰权限为只读,而引用对象的访问权限是可读可写的,权限增大了(不允许)
  const int a = 10;
  int& ra = a; 
  // 2.正常编译,b可读可写,b1权限为只读,权限缩小;至于b2,权限为可读可写,也是可以的
  int b = 10;
  const int& b1 = b;
  int& b2 = b;
  return 0;
}


变量之间的赋值没有权限缩小和放大的关系,因为它们只是单纯的传值,不涉及传地址。

int main()
{
  // 可以通过,只是单纯的传值,不涉及权限的缩小和放大
  const int a = 10;
  int b = a;
  return 0;
}


4. 中间(临时)变量


4.1 临时变量的产生


涉及到隐式类型转换,都是右值产生一个中间变量先进行值传递,然后中间变量在和左值进行相应操作的。在进行强制类型转换和函数调用后值返回时也会产生临时变量,临时变量都具有常性。


20210523112012849.png

4.2 临时变量的生命周期

20210523113314135.png


5. 使用场景


5.1 做参数


可以作为输出型参数传入

提高效率,没有了形参压栈和出栈的时间消耗

//类似于C语言传地址,不过减少了对地址的解引用操作,可以直接修改外面的对象
void Swap(int& left, int& right)
{
  int temp = left;
  left = right;
  right = temp;
}


5.2 做返回值


可以修改返回值,比如实现普通对象的operator[ ]时就可以用引用返回,达到修改的作用。

函数的引用返回可以少创建一个临时对象,提高效率

如果返回的变量出了函数作用域还存在就可以用引用返回,否则不安全

传值返回是多申请一块空间(临时对象),存放要返回的对象的值;而传引用返回,返回的就是返回对象本身。

//传引用返回
int& Count()
{
  static int n = 0;
  n++;
  //返回n本身
  return n;
}
//传值返回
int Count()
{
  static int n = 0;
  n++;
  //返回n的值的一份临时拷贝对象
  return n;
}


5.3 说明


引用返回造成的非法访问

20210523115307171.png

6. 引用和指针的区别


语法概念上引用就是一个别名,和其引用实体共用同一块空间;而指针就是一个存放地址的变量。在底层实现上,引用是按照指针方式来实现(指针访问对象是显示解引用,而引用是编译器自己做了类似指针解引用的处理)。


引用在定义时必须初始化,指针没有这个要求。

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

没有NULL引用,但有NULL指针。

在sizeof中含义不同:引用结果为引用对象类型的大小,但指针的大小始终是地址空间所占字节个数(32位平台下占4个字节,64位平台8个字节)。

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

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

访问实体方式不同,指针需要显式解引用,引用的话引用变量就是实体。

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


六. 内联函数


1. 概念


以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方把函数内容展开,从而替换对函数的调用,没有函数压栈的开销,内联函数可以提升程序运行的效率。


//定义两个数相加的内联函数
inline int Add(int a, int b)
{
  return a + b;
}


2. 特性


inline是一种以空间换时间的做法,省去调用函数栈帧的开销。所以代码很长或者有递归的函数不适宜使用作为内联函数。

inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有递归或者代码很长等等,编译器优化时会忽略掉内联。

inline不建议声明和定义分离,这样会导致链接错误。因为inline既要要被展开,就没有函数地址了,链接就会找不到。


3. C++替代宏的方法


3.1 宏的优缺点

优点


增强代码的复用性

提高性能(减少了函数调用栈帧的开销)

缺点


不方便调试宏。(因为预编译阶段进行了替换)

致代码可读性差,可维护性差,容易误用。

没有类型安全的检查


3.2 替代宏的方法


常量定义 :换用const来修饰

函数定义: 换用内联函数


七. C++11的几点新玩法


1. auto关键字(C++11)


1.1 auto简介


C++11中,使用auto定义变量时会自动识别变量的类型。使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

int a = 10;
//auto自动识别b的类型为int
auto b = a;


1.2 auto的使用细则


用auto声明指针类型时,用auto和auto*没有任何区别,因为右值就是一个地址;但用auto声明引用类型时则必须加&。

int x = 10;
//声明指针类型
auto a = &x;//等价于auto* b = &x;
//声明引用,必须加上&
auto& c = x


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

auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型


1.3 auto不能推导的场景


auto不能作为函数的形参类型

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导,也就不能完成传入实参的类型检查
void TestAuto(auto a)
{}


auto不能直接用来声明数组的元素类型

// 此处代码编译失败,auto不能用来声明数组的元素类型
void TestAuto()
{
  auto arr[] = {4,5,6};
}


1.4 说明


早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量。为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。

auto在实际中最常见的优势用法就是搭配C++11提供的基于范围的for循环一起使用,还有lambda表达式等。


2. 基于范围的for循环(C++11)


2.1 范围for的语法


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


PS:与普通循环类似,可以用continue来结束本次循环直接进入下一次,也可以用break来跳出整个循环。


2.2 范围for的使用条件


for循环迭代的范围必须是确定的

对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,begin()和end()就是for循环迭代的范围。

// 1.错误使用
//因为arrzuo作为实参参入后已经不是数组了,退化成了指针,for的范围不能确定
void TestFor(int arr[])
{
  for (auto& e : arr)
  {
  cout << e << endl;
  }
}
int main()
{
  int arr[] = { 1,2,3,4,5 };
  // 2.正确使用,此时arr代表整个数组,是有确定的范围的
  for (auto& e : arr)
  {
  cout << e << " ";
  }
  return 0;
}



迭代的对象必须要实现++和==的操作,因为范围for的底层就是通过这两种操作完成的。


2.3 实际使用时范围for中auto的修饰总结


如果不修改元素的值,仅仅使用这个值的话,auto前面用const来作为元素类型。

如果要修改元素的值或者元素类型非基本类型的话,auto加上引用来作为元素类型,这样对前者而言相当于传指针的作用,对后者而言可以减少一次拷贝。


3. 空指针nullptr(C++11)


NULL 预处理后:0

nullptr 预处理后:(void*)0


PS:C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。


在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。


相关文章
|
6月前
|
存储 C++ 索引
C++ 字符串完全指南:学习基础知识到掌握高级应用技巧
C++的字符串使用`string`类处理,如`string greeting = &quot;Hello&quot;`。字符串连接可通过`+`或`append()`函数实现。访问字符使用索引,如`myString[0]`。`length()`或`size()`可获取长度。`getline()`用于读取整行输入。注意转义字符如`\\&quot;`用于在字符串中嵌入双引号。使用`cin`读取字符串时,空格会终止输入,而`getline()`能读取整行。
62 0
|
4月前
|
C语言 C++ 开发者
C++基础知识(一:命名空间的各种使用方法)
C++在C的基础上引入了更多的元素,例如类,类的私密性要比C中的结构体更加优秀,引用,重载,命名空间,以及STL库,模板编程和更多的函数,在面向对象的编程上更加高效。C语言的优势则是更加底层,编译速度会更快,在编写内核时大多数都是C语言去写。 在C++中,命名空间(Namespace)是一种组织代码的方式,主要用于解决全局变量、函数或类的命名冲突问题。命名空间提供了一种封装机制,允许开发者将相关的类、函数、变量等放在一个逻辑上封闭的区域中,这样相同的名字在不同的命名空间中可以共存,而不会相互干扰。
|
4月前
|
C++
C++基础知识(二:引用和new delete)
引用是C++中的一种复合类型,它是某个已存在变量的别名,也就是说引用不是独立的实体,它只是为已存在的变量取了一个新名字。一旦引用被初始化为某个变量,就不能改变引用到另一个变量。引用的主要用途包括函数参数传递、操作符重载等,它可以避免复制大对象的开销,并且使得代码更加直观易读。
|
4月前
|
算法 编译器 C++
C++基础知识(三:哑元和内联函数和函数重载)
在C++编程中,"哑元"这个术语虽然不常用,但可以理解为在函数定义或调用中使用的没有实际功能、仅作为占位符的参数。这种做法多见于模板编程或者为了匹配函数签名等场景。例如,在实现某些通用算法时,可能需要一个特定数量的参数来满足编译器要求,即使在特定情况下某些参数并不参与计算,这些参数就可以被视为哑元。
|
4月前
|
C++
C++基础知识(四:类的学习)
类指的就是对同一类对象,把所有的属性都封装起来,你也可以把类看成一个高级版的结构体。
|
4月前
|
自然语言处理 程序员 C++
C++基础知识(五:运算符重载)
运算符重载是C++中的一项强大特性,它允许程序员为自定义类型(如类或结构体)重新定义标准运算符的行为,使得这些运算符能够适用于自定义类型的操作。这样做可以增强代码的可读性和表达力,使得代码更接近自然语言,同时保持了面向对象编程的封装性。
|
4月前
|
存储 编译器 C++
C++基础知识(六:继承)
多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。
|
4月前
|
存储 编译器 C++
C++基础知识(七:多态)
多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。
|
4月前
|
存储 算法 程序员
C++基础知识(八:STL标准库(Vectors和list))
C++ STL (Standard Template Library标准模板库) 是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如 queues(队列), lists(链表), 和 stacks(栈)等. STL容器的提供是为了让开发者可以更高效率的去开发,同时我们应该也需要知道他们的底层实现,这样在出现错误的时候我们才知道一些原因,才可以更好的去解决问题。
|
4月前
|
算法 前端开发 C++
C++基础知识(八:STL标准库 deque )
deque在C++的STL(Standard Template Library)中是一个非常强大的容器,它的全称是“Double-Ended Queue”,即双端队列。deque结合了数组和链表的优点,提供了在两端进行高效插入和删除操作的能力,同时保持了随机访问的特性。