c++杂记

1:类模板可以使用虚函数,但类模板成员函数不可以是虚函数(因为编译器无法确定存在多少虚函数)

2:复合类型:基于其他类型定义的类型(如:指针、引用);

3:顶层const和底层const(指针中的概念):顶层const指修饰指针本身,底层const修饰实际指向的;

int a = 0;
int const *  const p = &a;
auto pp = p;//pp的类型是 const int *

4:constexpr只对指针有效,如constexpr int *q = nullptr中的q不是指向一个constexpr int的对象,而是指q是一个constexpr的指针;

5:auto声明:auto一般会忽略掉顶层const,但保留底层const;

6:decltype:变量是啥,它就是啥;

7:关于constexpr,不能将自定义类型声明为constexpr,但实际测试结果如下:

#include <string>

class T
{
public:
    int t1;
    int t2;
};

constexpr T t = { 5 }; //vs2017 编译通过

constexpr std::string str = "hello world"; //vs2017 编译不通过


/分割线/

#include <string>
class T
{
public:
    T(int a) : t1(a) {}

    int t1;
    int t2;
};

constexpr T t = { 5 };                     //vs2017 编译不通过
constexpr T t(5);                          //vs2017 编译不通过
constexpr std::string str = "hello world"; //vs2017 编译不通过

8:using声明只能加入一个,例如不能:

using std::cin, std::cout;//错误
using std::cin; using std::cout;//正确

9:如果在构造函数或析构函数中调用虚函数,那么虚函数不再是虚函数,也就是说无效

10:不可更改常量属性;

11:关于初始化列表

#include <iostream>

struct A
{
    int a;
    double b;
};
struct B
{
    B(double bb, int aa) : a(aa), b(bb) {}
    int a;
    double b;
};

int main()
{
    A a = { 1.1, 2.2 };
    B b = { 1.1, 2.2 }; //注:vs编译下,报错C2398,应为B b = { 1.1, 2 };
    
    int c = { 3.3 };//按照c++ primer的说法,除了上面那句,此句也应该报错

    std::cout << a.a << " " << a.b << std::endl;
    std::cout << b.a << " " << b.b << std::endl;
    
    return 0;
}
//
1 2.2
2 1.1

10:非常量的引用必须为左值,即是不是常量,如果不是,则必须是左值(当然也有是常量的左值)

11:at做越界检查,【】不做,只要使用【】都要确认其位置有值

12:容器容纳着“对象”,而引用不是对象,所以不存在包含引用的容器(编译不通过)

13:因为{}可以直接用来构造对象,所以对于容器来说也可以用{}来构造;这样就有如下问题:

#include <iostream>

#include <vector>
#include <string>

int main()
{
    std::vector<std::string> v1 { "10", "11" };//10代表第一个元素的值
    std::vector<std::string> v2 {  10 , "11" };//10代表元素数量

    std::vector<std::string> v3 { "10" };//10代表第一个元素的值
    std::vector<std::string> v4 {  10  };//10代表元素数量
    

    std::cout << v1.size() << " " << v2.size() << std::endl; // 结果:2 10
    std::cout << v3.front() << " " << v4.front() << std::endl; // 结果:10 空

    return 0;
}

14:关于c++ifstream中EOF的问题:

//例如“123”
//则对应的是 开始位置 字符1位置 字符2位置 字符3位置 EOF
//一开始是指向“开始位置的”
//所以
//while(in.eof()){}会多读一次
//解决whi(true){ in.get(); if(in.eof()) break; ... }

15:迭代循环体内,不要向容器添加元素,否则迭代器将会失效;

16:数组大小必须大于0,否则编译出错;也就是说int a[0];编译不过;

17:数组初始化:

const unsigned size = 3;
int a1[size] = { 0, 1, 2 }; //
int a2[]     = { 0, 1, 2 }; //
int a3[5]    = { 0, 1, 2 }; //等价于 a3[] = { 0, 1, 2, 0, 0 };
string a4[3] = { "hi", "hello" }; // 等价于a4[] = { "hi", "hello", "" };
int a5[2]    = { 0, 1, 2 }; //错误

18:不能直接把数组赋值给另一个数组,即int a[] = { 0, 1 }; a2[] = a;

19:在很多用到数组的地方,编译器会将其自动替换成指针,例如:

 int a[] = {1};
 auto ia2(a);//ia2是一个指针
 decltype(a) ia3 = { 1 }; //ia3是一个数组,在此印证使用decltype时,对方是啥,我就是啥

20:可以取数组之外的地址,但别使用,例如:(推荐使用标准库中的begin和end函数,也可以使用范围for哦,注意这里的begin和end不是迭代器,而是函数)

int a[3] = { 0, 1, 2 };
int *pEnd = &a[3];

for(int *p = a; p != pEnd; ++p)
    std::cout << *p << std::endl;

/*
等价for循环
for(auto it = std::begin(a); it != std::end(a); ++it)
    std::cout << *it << std::endl;
*/

21:不要对超出数组大小的地方解引用,否则后果自负;

22:指向不同对象的指针虽然也可以比较(即任意指针皆可比较,包括空指针),但一般毫无意义;

23:内置下标运算符可以处理负值,比如数组,

int a[] = { 1, 2, 3 };
int *p = &a[1];
int i = p[-1]; //i = 1

24:范围for对内置数组也有用,例如:

int a[] = { 1, 2, 3 };

for(const auto &i : a)
    std::cout << i <<std::endl;

//1 2 3

int a[][2] = { { 0, 1 }, { 2 }, { 3 } };
for(const auto &row : a)
    for(auto i : row)
        std::cout << i << std::endl;

//0 1 2 0 3 0

//但下面编译出错
//row在这里不在是数组,而是指针
for(const auto row : a)
    for(auto i : row)
        std::cout << i << std::endl;

25:sizeof运算符关于数组:

int a[] = { 0, 1, 2 };
std::cout << sizeof(a) << " " << sizeof(*&a) << std::endl;

//12 12
//即3*sizeof(int) 3*sizeof(int)

26:c++中赋值语句得到是一个左值(可修改的左值哦)

27:关于求值顺序的问题:

/*
 * 注:明确了求值顺序的只有:&& ||  ?:  , 
 */

//在下面这条语句之中,表达式f1() 和 表达式f2()无法判断哪个先执行
int i = f1() * f2();

//理解:即 乘法用到的是表达式f1()和表达式f2()的结果,所以只要是结果就好


//表达式i和表达式++i不知道哪个先执行
int i = 0;
std::cout << i << " " << ++i << std::endl;

//理解:即 <<用到的是表达式i和表达式++i,所以只要是结果就好


//特别注意,求值顺序和 优先级、结合律都无关
//如:

int i= f() + g() * h() + j();
//虽然乘法一定比加法优先(优先级),并且f() + g() * h() 一定先相加(结合律:左结合),但是先执行哪个函数并没有规定

28:一元符,成员选择,乘除余,加减法,左右移,比较符,等非等,与,异,或,逻辑与,逻辑或,三目,赋值,复合赋值,异常,逗;

29:.和->都是成员运算符,不是成员的话会报错,但是.*和->*虽然也必须是成员,但是可以是不一样的名字,例如:

#include <iostream>

class A
{
public:
    A(int i) : p(new int) { *p = i; }

    int get() { return *p; }

    int *p;
};


int main()
{
    A a(2);
    A *pA = &a;

    int *(A::*p) = &A::p;
    int(A::*pFun)() = &A::get;

    std::cout << "."  << " " << *a.p << std::endl;
    std::cout << ".*" << " " << *(a.*p) << std::endl;

    std::cout << "."  << " " << a.get() << std::endl;
    std::cout << ".*" << " " << (a.*pFun)() << std::endl;

    return 0;
}
. 2
.* 2
. 2
.* 2
 

30:算术(+、-、*、/)运算、逻辑(!、&&、||)、关系(<、<=、>、>=)运算的运算对象和结果都是右值

int a = 0;
int b = 0;

int c = a + b;     //参与运算的都是右值哦,并且结果也是右值哦
double d = a + b;

31:整数相除是整数,整数和浮点相除,整数会被提升,除法中只有符号相同才为正,结果向0取整(即直接去除小数部分),取余的符号看前面的

32:注意:整数和bool值比较的问题;例如:

int a = 3;
if(a == true) { std::cout << "true" << std::endl;}
if(a == false) { std::cout << "false" << std::endl;}

//没有任何输出哦,解释:上面的true或者false会转成int

33:赋值运算的结果是左侧运算对象的类型,且是一个左值;赋值运算是满足右结合律的;

int b = 1;
(b = 1) = 2;
//b = 2

34:自增运算符,前置得到左值,后置得到右值;

35:三目运算符的优先级很低,所以:

int a = 1;

std::cout << a > 0 ? " a > 0" : " a < 0 " << std::endl; //编译不过
std::cout << (a > 0) ? " a > 0" : " a < 0 "; //输出1,然后根据cout的值是true还是false产生字符,是产生,不是输出哦
std::cout << (a > 0 ? " a > 0" : " a < 0 ") << std::endl;//正常 a > 0

36:对于位运算符(~、<<、>>、&、^、|),处理符号位时没有明确规定,所以建议将将这些符号用于无符号

unsigned char bits = 0233; //10 011 011

bits << 8; //bits提升为int,然后想左移动8位,移出的位被抛弃,如果向右移且有符号,插入的值未定义

37:sizeof不会计算表达式的值,因此再表达式中解引用无效指针也不会出错,c++11允许使用作用域来直接获取类成员的大小,sizeof是右结合的,其返回值是一个常量表达式即constexpr

#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
  vector<bool> v;
  string str("12345678913112112312131321311321231313212");
  string str2 = str;
  std::cout << sizeof(std::string) << " " << sizeof(str) << " " << sizeof(str2) << " " << sizeof(vector<bool>) << " " << sizeof(int*) << std::endl;
}

//cpp.sh
8 8 8 40 8
 

38:整形提升:负责较小整数转换成较大的整数类型

//对于bool, char, signed char, unsigned char, short, unsigned short 
//等类型来说,只要他们所有可能的值都能存在int里,那么他们就会被提升成int类型,否则提升成unsigned int;

//对于较大char类型(wchar_t, char16_t, char32_t),会提升成
//int, unsigned int, long, unsigned long, long long, unsigned long long中最小的

39:在很多用到数组的表达式中,数组往往会自动转换成指向数组首元素的指针,但在用作decltype、&(取址)、sizeof、typeid,赋值给引用,等运算符时不会转换

40:关于const_cast

const char *pc;
char *p = const_cast<char*>(pc);//正确,但是通过p写值是未定义的行为
//为啥

41:关于旧式的强制类型转换,如果替换成const_cast或者static_cast也合法,则其行为和对应的转换一致,如果不合法,则和reinterpret_cast类似

42:多余的空语句并非总是无害的(如if、while后面加;)

43:表达式语句是表达式加上分号

44:c++规定else匹配最近的if

45:关于范围for:
 

for(auto r : v)
{
//
}
//等价于
for(auto begin = v.begin(), end = v.end(); begin != end; ++begin)
{
    auto r = *begin;
//
}

46:break负责终止离它最近的while、do while、for、switch,所以除这些外的其他语句不能使用break;

        continue:只能出现在for、while、do while循环的内部;

        goto:负责从goto语句无条件的跳转到同一函数内的另一条语句;

47:函数返回值,返回的是啥就是啥,具体看你怎么用//????????

48:当函数的实参初始化形参的时候回忽视掉顶层const,例如

//不行,提示fun已有实体
void fun(const int  i) {}
void fun(int i) {}

//可以,是底层const
void fun(const int * i) {}
void fun(int * i) {}

//不行,提示fun已有实体
void fun(int * const i) {}
void fun(int * i) {}

//例子:证明忽略了顶层const
void fun(int * const p) {} //等价于void fun(int *p) {} 
int *p = nullptr;
int * const pp = nullptr;
fun(p), fun(pp);//编译通过,没问题

//不行,提示f已有实体
void f(const int*) {}
void f(const int[]) {}
void f(const int[10]) {}

//void function(int (&a)[10]) {}它的意思是只能是数组,且只能包含10个元素
//虽然很多情况下,向函数中传入数组,数组会自动转成指针,但是上面这个引用不会

//而对于void function(int (&a)[]) {},基本上调用不了(自认为)

49:void也可食用return expression形式,不过return中的expression必须也是void类型:如返回一个 返回void的函数,如:

void fun();

void f() { return fun(); };

50:值是如何被返回的:返回一个值的方式和初始化一个变量或形参的方式完全一样(c++ primer)

51:返回数组的指针

typedef int (*fun(int))[10];
fun
fun(int)
*fun(int)
(*fun(int))[10]//这个指针指向一个数组,数组大小为10
.......

52:名字查找发生在类型检查之前,编译器一旦在当前作用域中找到了声明,编译器就会忽略掉外层作用域的同名实体

string read();
void print(const string &);
void print(double);

void fun(int ival)
{
    bool read = false;
    string s = read();        //错误,read是一个bool值
    void print(int);          //函数声明
    print("value is : ");     //错误,隐藏了之前的print
    print(ival);              //正确 print(int)
    print(3.14);              //正确 print(int)
}

//分割线/
#include <iostream>
#include <string>
using namespace std;
void print(const string &)
{
    std::cout << "string" << std::endl;
}
void print(double)
{
    std::cout << "double" << std::endl;
    
}
void print(int)
{
    std::cout << "int" << std::endl;
    
}
void fun(int ival)
{
    void print(int);          //函数声明,不能在这里定义,否则报 error: a function-definition is not allowed here before '{' token

    print(ival);              //正确 print(int)
    print(3.14);              //正确 print(int)
}
int main()
{
    fun(1);
}

//
int
int
 

53:constexpr函数可以有多个定义,但定义必须一致,也就是说可以放在头文件中,被多次包含,但

constexpr int f() { return 43; }
constexpr int f() { return 43; }

int main()
{
    f();
    return 0;
}

//这样会被提示重复定义
//不过作为头文件包含可以,已经试过

54:this是一个常量指针(无法更改this的指向),类中成员函数后面的const是修饰this的,表明this指向一个常量

55:=default,即希望这个函数的作用完全等于之前使用的合成默认构造函数(即编译器创建的构造函数),不需要再添加实现了,编译器会实现的,不过编译器生成的对于内置类型和复合类型来说,他们的值是未定义的,你需要类内初始化,和构造函数初始值列表不同,自己写的不需要参数的就叫默认构造函数

56:使用class和struct定义类的唯一区别是默认的访问权限

57:友元位置随便,访问说明符(public、protected、private)对其无用,并且声明友元之前可以没声明,也就是说,可以再友元声明之后的地方声明函数或者类;(注:有的编译器并不强制执行上述关于友元的限定规则);

58:关于inline指定,虽然在声明和定义处可以同时添加,不过最好只在类外部定义的地方说明inline.

59:mutable data member:可变数据成员,即使是const,也可以进行修改;

60:当存在类的声明时,就可以声明定义类的指针,当一个类还未完全定义完时但类名已经出现了,它就被认为声明过了,所以可以声明定义它的指针;

61:一般来说(注:编译器可能不同,如vs2017可以,但cpp.sh不行——error: changes meaning of 'Money' from 'typedef double Money' [-fpermissive]),如果成员已经使用了外层作用域的某个名字,并且改名字是一个类型,则类不能再之后重新定义改名字。例如:

typedef double Money;
class A
{
public:
    Money balance() { return bal; }  //此处已经使用外层的Money
private:
    typedef double Money; //错误,不能重新定义Money
    Money bal;
};

62:构造函数初始化成员变量时,成员变量只能初始化一次,也就是说你想写两次,编译直接出错;另外,关于顺序问题:

#include <iostream>
class A
{
public:
    //行为未定义:i早于j初始化,但此时i一般是0,看编译器
    A(int val) : j(val), i (j) {}
    int getI() { return i; }
    int getJ() { return j; }
private:
    int i, j;
};

int main()
{
    A a(1234);
    std::cout << a.getI() << " " << a.getJ() << std::endl;
}
//
//cpp.sh
0 1234

#include <iostream>
class A
{
public:
    A(int val) : j(val), i (val) {} //waring
    int getI() { return i; }
    int getJ() { return j; }
private:
    int i, j;
};

int main()
{
    A a(1234);
    std::cout << a.getI() << " " << a.getJ() << std::endl;
}
//
//cpp.sh
1234 1234

63:聚合类定义:

//1:所有成员都是public
//2:没有定义任何构造函数
//3:没有类内初始值
//4:没有基类,没有virtual函数,
//如:
struct Data
{
    int val;
    std::string s;
};

//初始化必须按照何时声明的顺序,否则报错

64:字面值常量类(了解即可,自认为)

class Debug
{
public:
    constexpr Debug(bool b = true) : hw(b), io(b), other(b) {}
    constexpr Debug(bool h, bool i, bool o) : hw(h), io(i), other(o) {}
    constexpr bool any() { return hw || io || other; }

    void set_io(bool b) { io = b; }
    void set_hw(bool b) { hw = b; }
    void set_other(bool b) { other = b };
private:
    bool hw;
    bool io;
    bool other;
};

//使用
constexpr Debug io_sub(false, true, true);//不能是变量
//............

65:类静态成员和普通成员的一些区别:

class A
{
public:
    A();
    //...
private:
    static A s_a;//正确,静态成员可以是不完全类型
    A *pA;//正确
    A a;//错误,数据成员必须是完全类型
};

///分割线///
#include <iostream>
class A
{
public:
    void print(int i = m_i) { std::cout << "i = " << i << std::endl; }
    static void set(int i) { m_i = i; }
private:
    static int m_i;
};
int A::m_i = 1111;
int main()
{
    A a;
    a.print();
    A::set(2222);
    a.print();
}
//cpp.sh
i = 1111
i = 2222

66:使用std::copy来达到容器之间的复制(容器类型不同也可以哦,比如list复制到vector)当然也可以使用迭代器参数来拷贝一个范围,例如:

vector<const char*> articles = { "a", "an", "the" };
forward_list<string> words(articles.begin(), articles.end());//这是正确的

67:array不支持assign和花括号赋值,但关于花括号,可以使用花括号来初始化,前提是花括号内的元素数量<=size

68:出了array外,swap函数不对任何元素进行拷贝、删除或者插入操作

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值