c++基础

c语言是结构化和模块化的语言,用于处理规模较小的程序。当问题需要高度抽象和建模时,c语言不适合。c++是基于c语言产生的,既可以进行c语言过程化程序设计,又可以以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计

namespace关键字:使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染。

如果像下面这种情况,在c语言中是解决不了的:

#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
  printf("%d\n", rand);
  return 0;
}

编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”

命名空间定义

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

我们有不同的命名空间定义方法:

1、正常的命名空间定义

命名空间中可以定义变量/函数/类型

namespace pearl
{
   int rand=10;
   int Add(int left, int right)
   {
      return left + right;
    }
   Struct Node
   {
      Struct Node* next;
      int val;
    };
}

2、命名空间可以嵌套

//test.cpp

namespace pearl1
{
   namespace pearl2
   {
      int Add(int a,int b)
      {
         return a+b;
      }
    }
}

3、同一个工程中可以出现命名相同的空间,并且编译器最终会合成在同一个命名空间当中

//test.h

namespace pearl1
{
   int Sub(int a,int b)
   {
      return a-b;
    }
}

注意:一个命名空间就定义了一个作用域,命名空间所有的内容都局限在该命名空间当中

命名空间的使用

比如我们在命名空间当中定义了一个变量,我们该如何把它调用出来呢?

namespace pearl
{
   int a=1;
   int b=0;
   int rand=10;
   int Add(int left, int right)
   {
      return left + right;
    }
   Struct Node
   {
      Struct Node* next;
      int val;
    };
}
int main()
{
   printf("%d",a);
   return 0;
}

// 编译报错:error C2065: “a”: 未声明的标识符

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

1、加空间名称以及域作用限定符

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

2、使用using将某个空间成员引入

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

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

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

这里a和b都能够被正常打印出来。 

c++输入和输出

#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
   cout<<"Hello world!!!"<<endl;
   return 0;
}

注意:

使用cout(标准输出对象(控制台))和cin(标准输入对象(键盘))时,必须包含<iostream>标准头文件以及按照命名空间使用方法使用std。

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

是流插入运算符,>>是流提取运算符。并且在c++中的输入和输出可以自动识别变量的类型。

std命名空间的使用惯例

在日常练习中,建议直接using namespace std即可。

如果在大型的开发项目当中,直接展开,会出现比较多的问题,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式就可以了。

缺省参数

概念

是在C++声明或定义函数时,为函数的参数指定一个默认值。在调用该函数时,如果没有为某个参数指定实参(即没有显式地传递一个值给这个参数),则该函数将自动采用该参数的默认值。如果调用时提供了实参,则使用指定的实参值。

分类

全缺省参数

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

半缺省参数

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

注意事项

1半缺省参数必须从右往左给,不能跳着给。

2缺省参数不能在定义和声明中同时出现。

如果声明与定义位置同时出现缺省参数,恰巧两个位置提供的值不同,那编译器就无法确定到底用哪个缺省值。

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

函数重载

概念

在同一作用域,使用相同名称且功能类似的同名函数,这些同名函数的形参列表不同,来处理不同数据类型的问题。

1、参数类型不同

#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
 cout << "int Add(int left, int right)" << endl;
 return left + right;
}
double Add(int left, int right)
{
 cout << "double Add(int left, int right)" << endl;
 return left + right;
}

2、参数个数不同

void Add()
{
   cout << "f()" << endl;
}

void Add(int a)
{
   cout << "f(int a)" << endl;
}


3、参数顺序不同

void f(int a, char b)
{
 cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
 cout << "f(char b, int a)" << endl;
}

            那么为什么c语言支持函数重载,而c++不支持呢

review:一个程序需要运行起来,需要经过预处理,编译,汇编,链接这四个过程。

实际的项目通常由多个源文件和多个头文件组成,在编译后链接前,当addB.obj中调用的函数地址只在addA.obj(在addA.cpp中定义)中存在,我们就会进行链接。链接器在看到addB.obj调用obj时,就会到addA.obj中的符号表找到Add的地址,然后链接到一起。

每个编译器都有自己的命名规则,那么链接器会在哪儿寻找Add函数呢?

c语言

在Linux下,采用gcc编译后,函数名的修饰不发生改变。

c++

在Linux下,采用g++编译后,函数名的修饰发生改变。 会在编译过程中将函数的参数类型信息(以及其他可能需要的信息,如模板参数等)添加到函数的名字中。

引用

定义

引用不是定义一个新的变量,而是原有的变量有了新的别名,并不需要为这个别名开辟一个新的空间,它和引用的变量公用一块存储空间。

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

int &ra=a;

引用变量必须和引用实体是同种类型的。

引用特性

1引用前必须初始化

2一个实体可以被多次引用

3一个引用变量名只能使用一次,不能再引用其他实体

(引用表面好像是传值,其本质也是传地址,只是这个工作由编译器来做)

常引用

下面我将会逐个讲解下面代码错误的地方,以及如何修正

void TestConstRef()
{
    const int a = 10;
    int& ra = a;  
    const int& ra = a;
    int& b = 10; 
    const int& b = 10;
    double d = 12.34;
    int& rd = d; 
    const int& rd = d;
}

    const int a = 10;
    int& ra = a;     错误 ,不能将非常量引用绑定到常量对象上

   const int& ra = a;  正确
   

2  int& b = 10; 

错误,不能将非常量引用绑定到字面量上

   const int& b = 10; 正确

3      double d = 12.34;  
    int& rd = d;   
 错误,类型不同

const int&rd=d; 正确

权限可以缩小,但不能被放大。

用法

1作为参数

void swap(int&left,int&right)
{
   temp=left;
   left=right;
   right=temp;
}

2作为函数的返回值

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

Add函数运行结束之后,栈帧就被销毁了,c变量就没有意义了。但是空间被回收指的是空间不能使用,仍然可以通过引用找到这个值。 

在调用函数结束后,如果返回对象还在(未还给系统),可以使用引用返回,如果返回对象已经还给系统,就要使用传值返回。

传值传址效率比较

采用传值返回时,返回的并不是值本身,而是该实参或者返回变量的一份临时拷贝,因此效率非常低下。

在语法层面上,引用是不占空间的。

但是在底层逻辑层面,引用需要占一块空间。

int main()
{  
   int a=10;
   int &ra=a;
   ra=20;
   
   int *p=&a;
   *p=20;
   return 0;
}

引用和指针的区别

1 引用必须要初始化,指针可以不用初始化

2 引用在引用时只能引用一个实体,而多个指针可以指向同一个实体

3sizeof的结果不同,引用中计算的是引用内容的大小,而指针是地址空间的大小

4引用中+1是引用的实体加一,而指针是偏移一个类型大小

5指针需要显示解引用,指针只需要自己处理

6引用比指针更加安全

内联函数

概念

以inline修饰的函数叫作内联函数,在gcc编译器下,只有内联函数才会展开

inline是一种以空间换时间的做法,在编译阶段,会用函数体替换函数调用。可能会使空间变大,但是,能够提升函数运行的效率。

一般建议将函数规模较小,频繁调用且不是递归函数的函数采用inline调用

inline不建议定义和声明分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到,一般都是在源文件中直接定义的。

inline只是一种建议,需要看此函数是否能够成为内联函数。

// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

宏的优缺点

优点

1 宏可以直接进行替换,增强代码的复用性

2 增强代码的性能

缺点

1可读性不强

2不方便调试

3不能进行安全检查

(1. 因为宏在预处理阶段进行替换,而不是在编译阶段进行类型检查
   2. 类型安全检查是指编译器在编译时对变量和表达式进行类型检查)

auto关键字

编译器会通过变量初始化的表达式来推断变量的类型。(编译器在编译时期推导而得)它并不是一种类型的声明,而是一种类型的占位符。编译器在编译时期会替换成实际的类型。

auto的使用细则

1 和指针一起使用时,加不加*都一样

2 和引用一起使用时,必须加&

3 在一行定义多个变量时,必须为同一类型,因为auto是根据第一个变量的类型定义推导其他变量的

auto a=&x;
auto *a=&x;
auto &a=x;

auto不能使用的场景:

1auto不能作为函数的参数,因为编译器无法对a的实际类型进行推导

int* a=NULL;
void add(auto a)

2auto不能用来声明数组

auto arr[10]={0};

for循环

for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

下面的代码for循环的范围不确定,是错误的:

void TestFor(int array[])
{
    for(auto& e : array)
        cout<< e <<endl;
}

迭代的对象要实现++和==的操作 

指针空值nullptr

如果我们采用如下方式定义:

int* p1=NULL;

如果有两个函数,一个定义的是void f(int*); 一个定义的是void f(int);

此时调用f(NULL);可能会被曲解为0,编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。

注意 1 sizeof((void*)0)和sizeof(nullptr)是一样的字节数

2为了提高代码的健壮性,最好使用nullptr

3 nullptr代表指针空值,不需要包含头文件,为关键字

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值