c++中的冷知识(看C++primer的总结)

最近在学习c++,参考书籍是c++primer第五版,总结下书里有意思的部分(不一定是难的,大部分是有点反常识和反感觉的点。)

1 指针下标是可以取负数的,string 和vector不行

  std::string nums[] = {"hello","world"};
    auto *p = &nums[1];
    std::cout<<p[-1]<<std::endl;


输出:
hello

怎么说呢,string vector也不一定报错,但是输出会混乱,可能和你想象中的结果不太一样...

2 该怎么说呢,这个例子,p和*p输出是一样的,p指向二维数组的首地址,所以p是个地址,二维数组的首地址是个一维数组,所以*p还是个地址,**p是一维数组的首地址,是个数字,这个时候取值才是真正的数字,好像总结不清楚,还是看代码把

  int int_arr[2][3] = {1,5,70,66,111,0};

    for(auto p = int_arr;p != int_arr+2;++p){
        for (auto q = *p; q !=*p+3 ; ++q) {
            std::cout<<p<<std::endl;
            break;
        }
    }
    std::cout<<"~~~~~"<<std::endl;
    for(auto p = int_arr;p != int_arr+2;++p){
        for (auto q = *p; q !=*p+3 ; ++q) {
            std::cout<<*p<<std::endl;
            break;
        }
    }
    std::cout<<"~~~~~"<<std::endl;
    for(auto p = int_arr;p != int_arr+2;++p){
        for (auto q = *p; q !=*p+3 ; ++q) {
            std::cout<<**p<<std::endl;
            break;
        }
    }


结果
0x7fffab059de0
0x7fffab059dec
~~~~~
0x7fffab059de0
0x7fffab059dec
~~~~~
1
66

    double j = 7/3;
    std::cout<<j<<std::endl;


输出:
2

3.decltype(r)和decltype(r+0)的区别

顺便一提,*p得到的是引用类型

  int i = 0;
    int &j = i;
    int *p = &i;
    decltype(j) b; //将会报错,引用类型的b必须初始化
    decltype(j+0) c;//通过!c为int类型
    decltype(*p) e;//将会报错,引用类型的b必须初始化
    decltype(*p+0) f;//通过!c为int类型
    

4.好像这个也不冷,应该挺基础的,但是觉得蛮好玩的,记下来,关系运算符不能连续使用。 

int main() {
    int i=4,j=3,k=2 ;
    if(i<j<k){
        std::cout<<"i小于j小于k"<<std::endl;

    } else{
        std::cout<<"关系不成立"<<std::endl;
    }
    return 0;
}


输出
i小于j小于k

5.相等性比较,int k=2;k==true是1.整型转换成bool型比较  还是 2.bool转换成整型比较呢 ?

int main() {
    int i=4;
    if (i== true){
        std::cout<<"整型转换成bool"<<std::endl;
    } else{
        std::cout<<"bool转换成整形比较"<<std::endl;
    }
    return 0;
}

结果
bool转换成整形比较

6.对于指针,*i++的意义如字面意义相同,但是*(i++)我以为会有不同,然而实际上*i++与*(i++)一模一样。 

int main() {
    int k[2]={1,2};
    int *i = std::begin(k);
    std::cout<<*(i++)<<std::endl;
    return 0;
}
输出
1

7.函数重载是可以使用const重载的,具体一点,是可以使用底层const重载的,顶层const由于会被忽略而无法重载

void fcn(int *i=0){
std::cout<<"普通fcn函数"<<std::endl;
}

void fcn(const int *i=0){
std::cout<<"底层const重载"<<std::endl;
}

void fcn(int *const i=0){ //将会报错:Redefinition
std::cout<<"顶层const重载"<<std::endl;
}

8.关于顶层const会被忽略,值得是在形参与实参匹配的时候会忽略掉顶层const进行匹配,如下

#include <iostream>

void fcn(const int *i=0){
int j = 3;

std::cout<<"非顶层const匹配顶层const成功"<<std::endl;
}
void fcn2(const int *const i=0){


std::cout<<"顶层const匹配顶层const成功"<<std::endl;
}
int main() {
const int j = 1;
const int *const p = &j;
fcn(p);
fcn2(p);
}


输出:
非顶层const匹配顶层const成功
顶层const匹配顶层const成功

当然需要注意的是,顶层const与底层const讨论的对象是指针或者引用,对于普通类型,其顶层const属性就是底层const属性,因此不会忽略,同样可以使用(顶层)const重载。

9.形参为string&类型的函数是不能传入“hellowored”这样的参数的,如果要传入,需要另外创建一个string类型的对象,然后在传入。但是const string&类型的函数可以!

void f1(const std::string &s){
   

}
void f2(std::string &s){


}
int main() {
    const int j = 1;
    const int *const p = &j;
    std::string ::size_type  ctr=0;
    std::string s = "hello";
    f1("hello");
    f2("hello");//报错
}

10数组定义的时候,第一维可以省略,而后面的维度是不能省略的,因为第二维以后是数据类型。例如

nt a[10]; a是变量,int是类型 即a有十个坑,里面放“int”类型数据。
int a[10][20]; a是变量 int [20]是数据类型 即a有十个坑,里面放“int [20]”类型数据。
int a[10][20][30]; a是变量, int [20][30]是数据类型 即a有十个坑,里面放“int [20][30]”类型数据。
即除和数组最近的[]外,都是数据类型,数据类型可以省略的话,就好比int a,你写成了a,把int省略掉,是错的。

多讲一点,机器为什么不把第一维定义成类型而把其他维定义成类型呢?有人觉得(我一开始也这样想),一共100个数据,我告诉机器有20列,那他应该能算出来有5行,同样我告诉他有5行,他应该也能算出有20列才是,为什么不行呢?

这是因为c++构造数组的时候,会把没有显示说明的内容补零,这样,他其实是不知道一共有100个数据的

 int arr2[][3]={{1,2,3},{4,5}};
   std::cout<<arr2[1][2]<<std::endl;

结果:
0

可以看到,我们是可以省略第二维以后的信息的,这样机器就会自动给二维以后的数据补0,但是第一维如果没有显示说明,那么机器就不会补0,你写多少个一维数据,他就是多少个,而如果显示说明一维的维数,那便可以自动补0了。

int arr2[3][3]={{1,2,3},{4,5}};
   std::cout<<arr2[2][2]<<std::endl;

结果:
0

11.typedef和using有什么区别呢?比如我们要用arri10来表示10个int数构成的数组,应该怎么写呢?

typedef int arri10[10];

using arri10 = int[10];

 其实一开始比较奇怪,这个[10]怎么一会儿在int一会儿在arri10后面呢?我是这样理解的,typedef使用的是申明的方式,比如我们申明一个10个数字组成的数组,我们的写法是int arr[10]={0}; ,这里,我们申明arr是10个元素的数组类型(从内向外看,第一个是类型,具体看法参见C++11primer 第五版103页),然后才是左面,arr是10个元素为int的数组,那放在typedef里面也是一样的,因为typedef写法就是申明式的写法,要符合申明的写法,于是[10]就跑到了arri10的右面。而using 使用的是引用的写法,再次举例,int i=0; int j = i;这里面,我们使用引用的方式告诉编译器,以后用j就是用i,即 a =b,放在using 里面,arri10 = int[10],意思就是说,arri10就是10个元素为int的数组,看前后两个写法,是不是含义一样呢?理解了含义后,以后使用typedef也好,using也好,习惯性把[]放在最后面就好了。

12.(1)int *p[10]; 和(2)int (*p)[10]有什么区别呢?

对于数组(尤其是带指针的数组),一定要遵循先内后外,先右后左,有括号看先看括号里面的,

(见C++11primer 第五版103页),所以,对于(1),首先看右面,他是10个元素的数组,然后看左面,数组元素的类型为指针,指针类型为int(或者把int *当做一个整体,则“然后看左边,数组元素为int型指针”),合起来就是,(1)是一个  (10个int型指针构成的)数组,对于(2),首先看括号,他是一个指针,然后看右面,他指向十个元素的数组,然后看左面,他的元素的类型为Int,合起来就是,(2)是一个 指向(元素为Int,长度为10的数组)的指针

13.其实对于int* p和int *p,有另外一种理解,*p说明(注意是在申明时)p具有解引用的功能, int *p说明p是一个int型的具有解引用能力的东西,能解引用的当然就是指针,所以他的理解就是,一个int型指针,如果可以这么理解的话,复杂一点的

int (*function1(int i))[10];

首先这是一个申明,从内向外从右向左看,function1说这是一个函数的申明,function1(int 1)说这是一个参数为int型的函数申明,(*function1(int i))说明这是一个参数为int型的可以解引用的函数的申明,(*function1(int i))[10]说明这是一个参数为int型的可以解引用得到一个长度为10的数组的函数的申明,int (*function1(int i))[10]说明这是一个参数为int型的可以解引用得到一个长度为10元素类型为int 的数组的函数的申明 再往后得到;说明这申明结束。可以解引用得到长度为10的元素为int的数组,换句话说,这个函数的返回类型是数组指针,而不是int

好像c++11觉得这样太反人类的,于是设计出了一种简单的等价写法:

auto function1(int i) ->int(*)[10]

好耶,直观多了!

14.同一个函数是可以申明多次的(不是重载),但是操作仅限于对默认形参的添加,并且顺序必须从后往前,即只有当第n个参数添加默认形参后,才能给第n-1个参数添加,顺序不能乱

void f1(int s,int h,char b='y');
void f1(int s,int h=1,char b);
void f1(int s=2,int h,char b);
//以上正确

void f1(int s,int h,char b='y');
void f1(int s=2,int h,char b);
void f1(int s,int h=1,char b);
//以上将会在第二行报错,在定义s之前,要求先定义h的默认形参

15,比int还短的数据类型,在向上提升类型的时候,都会直接转换成int,而非“最小代价提升”,我觉得这是因为硬件设计的原因,总所周知现代计算机一个字都是64位或者32位,而int刚好是32位(据说16位的操作系统中int占16位,不太清楚),这样貌似数据读取会方便很多?(不太确定,寄存器相关知识仅限于本科学习接触)

#include <iostream>
void f1(int){
    std::cout<<"int"<<std::endl;
}
void f1(short){
    std::cout<<"short"<<std::endl;
};
int main(int argc,char *argv[]) {
    f1('s');
    return 0;
}


结果
int

16.函数指针与返回类型是指针的函数的区分

主要在于距离变量名最近的是指针符号还是参数列表符号,借用13的例子

int (*function1(int i))[10];

对于这句话,我们第一眼看到的是(int i),因此可以确定他是函数而不是指针(先右后左,由内而外原则)

而对于

int (*(*function1)(int i))[10];

则第一眼看到的是指针符号,因此可以确定他是指针而不是函数。

具体而言,

(*function1) 这是一个指针,

(*function1)(int i)) 指向参数为int的函数,

(*(*function1)(int i))这个函数拥有解引用操作,即返回类型为指针,

(*(*function1)(int i))[10]返回值解引用后为长度为10的数组,

int (*(*function1)(int i))[10]该数组元素类型为int,

int (*(*function1)(int i))[10];这是一个指向参数为int型的可以解引用得到一个长度为10元素类型为int 的数组的函数的指针的申明。

顺便说一句,auto真香

int (*function1(int i))[10]{
    std::cout<<"调用成功"<<std::endl;
};

int main(int argc,char *argv[]) {

    function1(1);
    int (*(*funptr)(int i))[10] = function1;
    auto funptr2 = function1; //funptr的等价写法
    funptr(1);
    funptr2(1);
    return 0;
}

结果
调用成功
调用成功
调用成功

mark一下,既然函数返回类型有指针,而指针可以指向函数,所以函数返回函数也是合理的

下面给一个例子

int (*(*function1)(int i))(int* j),以及他的真香版本写法

auto *function1(int i) ->int (*)(int* j)

注:(*)括号不能少,如果没有括号,说明返回类型为int*,带括号说明返回类型为函数指针,可以这么理解,函数不关心返回值的名称,因此他把名称省略掉了,我们把名称p加回去,也就是->int int (*p)(int* j)显然这样和int *p(int* j )完全不一样。

17.关于this指针,由于this必须指向调用者,因此其为一个顶层const指针,默认情况下不具有底层const属性,因此,对于一个常量对象,我们并不能调用普通的函数。为了让常量对象调用函数,需要将this申明成底层Const,做法是在参数列表后面加一个Const,例如,func1()const{};

class A{
public:
    void function1(){
        std::cout<<"这是一个函数"<<std::endl;
    }
private:
    const int k=0;
};
int main(int argc,char *argv[]) {

  const A a  = A();
  a.function1();//报错,this没有底层const属性
    return 0;
}

我们的做法是将function1写成一下形式 

void function1()const{
        std::cout<<"这是一个函数"<<std::endl;
    }

这样做的好处是,不论常量对象还是非常量对象,都能调用这个函数。

当书写函数的时候,如果确定不会修改属性的值,将函数修改成const是个好的习惯。因为当你试图修改const对象的属性的时候,编译器会报错提醒

class A{
public:
    void function1()const{
        std::cout<<"这是一个函数"<<std::endl;
        k=1;//报错,常量函数不能修改属性

    }

private:
    const int k=0;
};

18.构造一个函数的时候,返回值类型该怎么设置呢?

#include <iostream>
class A{
public:
    A& function1(int j){

        k+=j;//报错,常量函数不能修改属性

    }
    int get_k()const{
        return k;
    }
    A& add_k(int j){
        this->k+=j;
        return *this;
    }
    void add_k_void(int j){
        this->k+=j;
    }
private:
     int k=0;
};
int main(int argc,char *argv[]) {

    A a  = A();
    A aa = A();
    a.add_k(1);
    aa.add_k_void(1);
  std::cout<<a.get_k()<<std::endl;
  std::cout<<aa.get_k()<<std::endl;
    return 0;
}


结果:
1
1

比如上面的代码 ,返回类型是void和引用都不影响结果,但是,还是引用的好,这样可以进行类似这样的操作

#include <iostream>
class A{
public:
    A& function1(int j){

        k+=j;//报错,常量函数不能修改属性

    }
    int get_k()const{
        return k;
    }
    A& add_k(int j){
        this->k+=j;
        return *this;
    }
    void add_k_void(int j){
        this->k+=j;
    }
private:
     int k=0;
};
int main(int argc,char *argv[]) {

    A a  = A();
    A aa = A();
    aa.add_k_void(1);
    aa.add_k_void(2);
  std::cout<< a.add_k(1).add_k(2).get_k()<<std::endl;
  std::cout<<aa.get_k()<<std::endl;
    return 0;
}

结果
3
3

发现,a比aa方便的多,就是因为a使用的是返回引用的函数。

19.流操作拥有四个状态(badbit,failbit,eofbit,goodbit),当遇到不可恢复错误时,流状态badbit置1,当遇到可恢复错误时,failbit置1,当遇到EOF时eofbit置1,否则goodbit置1

对于流操作的s相当于s.good(),例如while(cin)等价于while(cin.good())

因此如下代码

 while(getline(std::cin,line)){
}

getline返回对cin的引用,因此当且仅当cin.good()返回0时while终止,一般通过EOF终止(badbit为系统崩溃,failbit为操作失败,例如期望数值而得到字符串。这两种都不太可能在getline()中发生)

linux中EOF为ctrl+d.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值