C++ 入门

 我根据所学,将入门分为十个小部分。如果有秃道同袍发现不对之处,还望多多指正,感激不尽。

1.关键字:对于关键字的学习,是学习编程语言的基础,这些关键字的含义和使用方式组成了基础的语法部分,此处不列举关键字,关键字的学习是贯穿整个语法部分的学习过程的。C++98里是63个关键字,C++11中73个。(C语言中32个关键字)。

2.命名空间:命名空间的引入是为了解决命名冲突。如果把所有变量、函数、类,名字都放在同一作用域,很容易出现傻傻分不清的情况,如果将它们放在不同的作用域,就能避免冲突。比如一个家庭里,兄弟两个的名字是一样的,会出现不知道叫的是谁这样的问题,如果同名的人在不同的地方,就很容易区分了,如西安小C,南京小C,虽然都叫小C,但是能区分叫的是谁。命名空间内的内容都局限于该命名空间。

使用关键字namespace定义命名空间。

namespace N1  //N1是命名空间的名字
{
    //在括号内定义的变量、函数、类就是该命名空间的成员
    int a = 10;
    int Add(int left, int right)
    {
        return left + right;
    }

}   //不加;号

命名空间可以嵌套定义

namespace N2
{
    int a = 5;
    int* a = &a;
    namespace N3  //嵌套的命名空间,命名空间N2内的命名空间N3
    {
        int a = 10;
        int *a = &a;
    }
}

同一作用域下的同名命名空间会合并成为一个命名空间,若它们内部存在同名成员则会出现错误。不在同一作用域下的同名命名空间不会合并为一个。

 

 

 对于命名空间内的成员的使用, 第一种方式:命名空间的名字+作用域限定符

namespace N1 
{
    int a = 10;
    int Add(int left, int right)
    {
        return left + right;
    }
}  

int main()
{
    int x = 11, y = 15;
    int sum;
    sum = N1::Add(x, y);
cout << N1::a << endl;
return 0; }

第二种方式:使用 using将命名空间内的成员引入

using N1::a;
int main()
{
    cout << a << endl;  //输出a
    return 0;
}

第三种方式:using namespace N1; 

using namespace N1;
// int a   //错误,此时N1的内容相当于全局变量,不可在同一作用域再有同名成员
int main()
{
    //int a = 5; //和N1中的a不是同一作用域,可以使用,且优先使用该局部变量
    cout << a << endl;  //输出a
    cout << N1::a << endl; //有同名局部变量时,用命名空间+作用域限定符来说明使用的是哪个变量
    return 0;
}

3.输入与输出:cout<<标准输出(控制台),cin>>标准输入(键盘),使用时要包含头文件<iostream>,以及标准命名空间 std。某些旧版本编译器支持使用<iostream.h>头文件。

#include<iostream>
using namespace std;

int main()
{
    cout << "hello world" << endl; //endl为换行
    return 0;
}
int main()
{
    int a;
    cin >> a; //单个输入a

    int b;
    char c;
    cin >> b >> c;  //连续输入b 和 c

    cout << a << endl;//单个输出a ,endl为换行
    cout << a << " " << b << " " << c << endl; //连续输出a 空格 b 空格 c
     
    return 0;
}

有关于输入输出的缓冲区问题此处暂时不讨论。

4.缺省参数:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该 默认值,否则使用指定的实参。(备胎,车上放一个备用轮胎……)

 

 

 

几种情况如代码所示:

int Add(int left, int right)
{
    return left + right;
}
int main()
{
    int a = 5, b = 6;
    int c = Add(a, b);
    // int d = Add(); //调用的函数得形参没有给出默认值,不传递参数无法调用函数
    return 0;
}
int Add(int left=10, int right=11)
{
    return left + right;
}
int main()
{
    int a = 5, b = 6;
    int c = Add(a, b);  //  c 的结果为11 ,传递实参则用实参的值
     int d = Add();    // d的结果为21,不传递实参则使用给出的默认值(缺省值)
    return 0;
}

函数的每个形参都给出缺省值,称为全缺省参数。

int Add(int fir=1, int sec=3,int thr=5 )
{
    return fir+sec+thr;
}
int main()
{
    int a = 5, b = 6,c=7;
    int x = Add();    // x的结果为9
    int y = Add(a);   // y的结果是13
    int z = Add(a, b); // z的结果是16
    int n = Add(a, b, c); // n的结果是18
    return 0;
}

可以发现函数传递参数是从形参列表的左边依次到右边。

只给函数的部分形参默认值,称为半缺省参数。

//int Add(int fir=1, int sec=3,int thr ) //编译报错
//{
//    return fir+sec+thr;
//}

//int Add(int fir = 1, int sec, int thr) //编译报错
//{
//    return fir + sec + thr;
//}

//int Add(int fir = 1, int sec, int thr=3) //编译报错
//{
//    return fir + sec + thr;
//} 

//int Add(int fir , int sec, int thr) //编译通过
//{                                   //没有给缺省值的函数
//    return fir + sec + thr;
//}

//int Add(int fir, int sec = 3, int thr=5) //编译通过
//{
//    return fir + sec + thr;
//}

int Add(int fir, int sec , int thr = 5) //编译通过
{
    return fir + sec + thr;
}

由于函数参数的传递方式是从左到右,所以决定了半缺省参数,只能从右往左给出,且中间不能间隔。

缺省值必须是常量或者全局变量。 (C语言不支持缺省值)。

缺省值不能再声明和定义中同时给出,声明或者定义二者选一个地方给出。

5.函数重载:同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数、类型、或顺序)必须不同,用来处理功能类似,数据类型不同的问题。

int Add(int left, int right)
{
    return left + right;
}
double Add(double  left, double right)
{
    return left + right;
}
int Add(int left, char right)
{
    return left + right;
}
int main()
{
    int a = 3, b = 5;
    int c = Add(a, b);
    cout << c << endl;

    double x = 11.1, y = 22.2;
    double z = Add(x, y);  //调用整形的Add会损失精度
    cout << z << endl;
    return 0;
}

实现函数重载的原理是,底层对函数编译的时候对函数名进行了修饰,将参数列表里的内容也使用了,所以能实现重载。C语言不支持函数重载,C语言底层对函数的修饰规则是在函数名前加下划线,如add,修饰为_add。VS下对C++工程中的函数名的修饰规则:

 

 

关键字extern:外部符号引入

extern   "C"   将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器, 将该函数按照C语言规则来编译。

extern "C" int Add(int left, int right) //按C语言方式编译,如果只给出声明,不给定义,会发现底层函数名为_Add
{
    return left + right;
};

 

6.引用:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它 引用的变量共用同一块内存空间。(在底层引用使用指针的方式实现的。)

引用的定义方式:

int main()
{
    int a = 10;
    int& ra = a; // 定义引用类型,引用与被引用的实体必须是同种类型
    auto& rA = a; //可以使用auto来定义引用
    return 0;
}

引用的特性:

1、引用在定义的时候必须初始化,不能出现空引用。

2、一个变量可以有多个引用,但是一个引用,只能引用一个实体,并且不能再引用其它实体。

int main()
{
    const int a = 10;   
    //int& ra = a;  // 该语句编译时会出错,a为常量   
    const int& ra = a; 

    // int& b = 10;  // 该语句编译时会出错,b为常量   
    const int& b = 10;

    double d = 12.34;   
    //int& rd = d;  // 该语句编译时会出错,类型不同   
    const int& rd = d;
    return 0;
}

引用可以作为函数形参和返回值类型。

//引用做形参
void swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}
int main()
{
    int a = 4, b = 7;
    swap(a, b);   //能该表外部实参  此处交换函数如果传值不能完成外实参的交换,传指针也可完成实参交换
    return 0;
}
//引用做返回值
int& test(int a)
{
    a += 3;
    return  a;
}

如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引 用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。

传引用的效率高于传值,因为传值要对参数进行拷贝,传引用不需要对参数进行拷贝。

引用在语法概念上,引用是一个别名,没有独立的空间,和被引用的实体是同一块空间,底层实现实际是有空间的,是按照指针的方式来实现的。

引用与指针的区别:

1.引用在定义的时候必须初始化,指针没有要求。

2.引用在引用一个实体后,不能再引用其它实体,而指针可以随时改变指向的实体(类型不变)。

3.没有NULL引用,有NULL指针。

4.在sizeof中的含义不同,引用的结果,是其引用实体的类型大小,指针始终是地址空间,一个整形大小。

5.引用做自加操作是引用的实体的增加1,指针自加是指针向后偏移一个类型大小。

6.没有多级指针,有多级指针。

7.访问实体的方式不同,对引用进行的操作就是对实体操作,编译器自己处理,指针访问实体时需要解引用。

8.引用比指针更安全,不会出现空引用。

 

7.内联函数:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。内联函数是一种对编译器的建议,编译器可能不会将其编译为内联函数。

inline int Add(int left, int right)
{
    return left + right;
}
int main()
{
    int a = 4, b = 7;
    int c = Add(a, b); //在此处替换为函数体
    cout << c << endl;
    return 0;
}

特性:

1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜 使用作为内联函数。 
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。

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

宏的优缺点:1、增强代码的复用性。2、提高性能。

宏的缺点:1、不方便调试。2、可读性差、可维护性差,容易误用。3、没有类型安全检查,可能会出现副作。

C++中替换宏的技术:

1、常量定义,使用const修饰

2、函数定义,使用inline修饰,将函数写为内联函数

8.auto关键字

 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有 人去使用它。

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

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

auto的使用细则 :
1. auto与指针和引用结合起来使用 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

int main()
{
    int a = 11;
    auto b = a;
    auto c = 'c';
    auto d = Add();
    //auto e;  //报错,使用auto定义变量时必须初始化
    //auto arr[10]; //报错,auto不能定义数组
    int* pa1 = &a;
    auto pa2 = pa1;
    auto* pa3 = pa1; //指针类型,*写不写都一样

    int& ra1 = a;
    auto& ra2 = a;//引用必须加&
    return 0;
}

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

void test(auto a) //报错,不能使用auto作为形参类型,因为编译器无法对a的实际类型进行推导
{}
int main()
{
    auto a = 1, b = 2;
    //auto c = 3, d = 4.0; //报错,类型必须一致
    //auto arr[10]; //报错,auto不能定义数组
    return 0;
}

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。

4. auto在实际中常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等 进行配合使用。

5. auto不能定义类的非静态成员变量(暂不做讲)。

6. 实例化模板时不能使用auto作为模板参数(暂不做讲)。

9.基于范围的for循环

对于数组,我们的遍历方式有传统的循环和范围for循环

int main()
{
    int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    for (int i = 0; i < 10; i++) // 传统循环
        cout << array[i] << " ";
    cout << endl;

    for (auto e : array)  //范围for循环,可用来遍历、循环赋值等
        cout << e << " ";  //范围for的使用,在STL中,迭代器的使用此处暂不讲
    cout << endl;
    return 0;
}

相比于传统循环,范围for代码更加简洁。

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

范围for的使用条件 :
1. for循环迭代的范围必须是确定的 对于数组而言,就是数组中第一个元素和后一个元素的范围;对于类而言,应该提供begin和end的 方法,begin和end就是for循环迭代的范围。

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

10.指针空值--nullptr:

       在良好的C/C++编程习惯中,声明一个变量时好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化 :

    int* p1 = NULL;  //在C中初始化没有合法指向的指针的方式是将0赋值给指针
    int* p2 = 0;     //NULL是一个宏,结果是0,或者((void*)0)

在使用空值指针的时候会遇到一些麻烦,例如重载单参的函数,将值为NULL的指针传递给函数,函数调用会不明确。

 

       程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下 将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

        为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新的nullptr表示空值指针。C++11为什 么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可 能不太相同,而且直接扩展NULL,可能会影响以前旧的程序。因此:为了避免混淆,C++11提供了 nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转 化为指针类型,nullptr_t被定义在头文件中:

typedef decltype(nullptr) nullptr_t;

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

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

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

 

posted on 2019-11-16 00:18  青椒炒肉没有肉  阅读(152)  评论(0编辑  收藏  举报

导航