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函数不对任何元素进行拷贝、删除或者插入操作