C++记录(二)
1.算术移位和逻辑移位。
逻辑移位是只补0,算术移位是看符号,负数补1,正数补0(讨论的是右移的情况下)。
负数左移右边一样补0。如果遇到位运算的相关题目需要对int变量进行左移而且不知道正负,那么先强制类型准换为unsigned再进行移位操作。
2.constexpr意义是字面值常量,即在编译期就可以将其计算出结果。
constexpr修饰函数名是高速编译器放心大胆的将该函数计算出结果(要满足形参和return的值都是常量才行)。
constexpr int f(int x)
{
return 6;
}
int main() {
int x = 1;
int p[f(x)]; //报错,因为形参x不是常量
int p[f(2)]; //成功
getchar();
return 0;
}
内联函数和constexpr函数定义都要放在头文件中,因为对这两种函数的优化都是在编译期做的,这个时候各个源文件之间还没有进行链接,如果放在某一个源文件里面(.cpp)
别的cpp根本就找不到这个函数的定义。 所有只有把函数定义放在头文件中,这样所有包含该函数的源文件都到该头文件中去才能找到该函数定义。
3.assert(x==4); //如果x等于4继续执行后面语句,否则跳出报错
在incude<assert.h>前面加#define NDEBUG 可以使assert失效,变为非debug模式。
4.类的复制构造函数如A(A other){pass},参数不能传值,因为传值会复制原对象为形参,这又需要调用复制构造函数,无限递归下去。
正确的方式是传引用。
5.类的成员函数参数列表后加const,相当于修饰的是对象的this指针。
举例:
class A
{
public:
void f ()const
{
cout<<"f:"<<x<<endl;
}
void g()
{
cout<<"g:"<<x<<endl;
}
A()=default;
private:
int x=1;
};
int main(int argc, char **argv)
{
A a;
const A b;
a.f();
a.g();
b.f();
b.g(); //这句会报错,常量调用非const方法,这样无法保证常量b的数据不被该方法篡改。
getchar();
return 0;
}
6.系统只要发现类里提供了构造函数,就不会再提供默认构造函数,所以写构造函数的话,一定把默认构造函数也加上。
7.一个const成员函数如果返回引用,该引用是常量引用,因为const成员函数的this指针是const A *const类型,
*this的类型是const A&(常量引用)。
8.类的友元函数是用来类之间数据通信的。类A的友元函数可以访问类B的private成员。
友元函数必须在类内声明,类外定义。
eg:
class Date;//对Date类的提前引用声明
class Time
{
public:
Time(int, int, int);//声明构造函数
void display(Date &); //display函数参数为date类型
private:
int hour;
int sec;
int minute;
};
class Date
{
public:
Date(int, int, int);//声明构造函数
friend void Time::display(Date &); //那么在date类里声明Time类的display函数为友元函数
private:
int mouth;
int day;
int year;
};
Time::Time(int h, int m, int s) //友元函数必须在类内声明,类外定义!!!!
{
hour = h;
minute = m;
sec = s;
}
9.指针作为函数参数,如果通过指针改变其指示位置存储的数据,是可以的。
但如果改变指针的指向,(指向另一块内存),从函数出去之后指针
仍然指向的是原来的区域。
原因:假设p为指针,函数f调用p,那么实际上会申请另一个指针p2=p,在函数
中将p2指向其他区域,是影响不到函数外指针p的指向的。而至于为什么可以改变p指向区域的数据,
是因为临时申请的指针p2指向的区域和p一样,故修改是可以成功的。
如果函数会修改指针的指向,那么要使用二级指针。
- ::是域作用符,下面代码会输出类A外面的a,而不是类的a。
int a=1;
class A
{
public:
int a=2;
void f()
{
cout<<::a;
}
};
int main()
{
A x;
x.f();
getchar();
}
11.const和引用变量必须初始化
类的成员变量如果有const或者引用类型的,如果不列表初始化或者直接在声明时就给定初始值,会报错!
类的列表初始化和正常的构造函数是有本质区别的!并且列表初始化比构造函数赋值要快,因为后者是先初始化再赋值。
12.如果一个类A有两个单参数的构造函数,那么这两个构造函数不可以都有默认实参,这样A x;会不知道调用哪个构造函数,即函数二义性。
普通函数不存在这个问题因为普通函数名不可能相同。
class A
{
int x;
int y;
public:
A(int a,int b):x(a),y(b){} //被委托的构造函数
A():A(0,0){} //委托最上面的构造函数进行构造
A(int a):A(a,0){} //委托最上面的构造函数进行构造
};
- default_random_engine只需要声明一次,随机数引擎每次初始化都是一个状态,会产生一模一样的随机数序列(所以应该叫伪随机数)。
uniform_int_distribution可以限制整形随机数的范围,uniform_real_distribution限制实数随机数的范围
eg:
int main()
{
default_random_engine e;
uniform_real_distribution<double> u(0,10000);
for(int i=0;i<1000000;++i)
{
cout<<u(e)<<endl;
}
15.
typedef int* p; //int*
typedef char q[5]; //长度5的char数组
typedef char *r[5]; //长度5的char*数组
typedef char (*s)[5]; //指向【长度为5的char数组】的指针
getline函数有两个,第一个声明如下,定义在fstream头文件里。
template<typename _CharT, typename _Traits, typename _Alloc>
inline basic_istream<_CharT, _Traits>&
getline(basic_istream<_CharT, _Traits>& __is,
basic_string<_CharT, _Traits, _Alloc>& __str)
{ return std::getline(__is, __str, __is.widen('\n')); }
该函数不是什么类的成员函数,所以调用时候直接getline(输入流,字符串)。第一个参数是输入流类,第二个参数是一个string引用。作用是从指定输入流读一行到string里保存。
另一个getline声明如下,定义在istream头文件里
__istream_type&
getline(char_type* __s, streamsize __n)
{ return this->getline(__s, __n, this->widen('\n')); }
然后往上翻它是属于basic_istream类的成员函数,所以调用格式是istream对象.getline(…)。
它的第一个参数是c字符串__s,第二个参数是字符串的长度__n。即从指定输入流读__n-1个字符到__s中,加’\0’结束。
template<typename _CharT, typename _Traits>
class basic_istream : virtual public basic_ios<_CharT, _Traits>
{
public:
stl的list模板迭代器只重载了++、–运算符,不能对list< int> ::iterator进行+=1这样的操作,迭代器也不可以互相比较大小。如有两个list::iterator迭代器l.begin()和l.end(),不可以写while(l.begin()<l.end())。其他顺序容器迭代器(如vector、deque、string等等)都是可以的,+=也是可以的。
begin(),end()
cbegin(),cend() //const_iterator版本
rbegin(),rend() //倒序版本
crbegin(),crend() //倒序const_iterator版本
18.emplace和insert函数的区别在于:当参数是一个类的实例时,emplace不必先构造临时变量再复制一个插入,它直接将数值传给相应容器的构造函数,只需要构造一次。insert要构造两次。其他emplace_back和emplace_front函数也一样。甚至emplace函数可以无形参调用,如下图
这样emplace_back会直接调用Bar的默认构造函数插入,但push_back函数不能没有形参,所以图中报错了。
double float等类型判等时不能直接用等于号,需要定义equal函数,判断两个量的值相减是否小于某个预设值(如1e-6)。
static_cast:只要不包含底层const,都可以用这个来强制转换。
const_cast:专门用来改变底层const
swap(stl1,stl2)可以交换两个同样类型的stl类型变量的内部数据。(速度很快,因为不会拷贝复制)
如:
vector<int> p1,p2;
swap(p1,p2);
list<int> l1,l2;
swap(l1,l2);
map<char,int> m1,m2;
swap(m1,m2);
- 线性stl容器(vector、list、forwar_list、string等)都有erase成员函数,一个是erase(iterator),一个是erase(iter1,iter2),其中iter2是删除的最后一个元素的下一个位置。这两个函数都返回最后删除元素的下一个位置的迭代器。
其中string除了上面两个共有的,还有一个单独的成员函数erase(pos,size),表示从pos索引开始删除size长度的子串,返回值是删除后的新字符串的引用。
共有insert成员函数,insert(iterator,num),将num插入到iterator前面,并返回新插入元素的迭代器。
-
静态成员函数只能操作静态成员变量。
静态成员变量在类内声明,类外定义。
静态成员函数可以在类内直接定义。 -
函数指针
int F(int (*f)(int)){
(*f)(6);
}
int f(int x){
cout<<x*x<<endl;
return 0;
}
int main(){
F(f);
getchar();
}
- 不想某个类被拷贝,用=delete来定义复制构造函数和赋值运算符重载。
=delete可以用来定义任何函数,以表示该函数不能使用。
=default相当于空函数体,只能用来定义默认构造函数和复制构造函数!!
26. noexcept用来修饰程序员确定不会抛出异常的函数。因为普通的函数是有可能抛出异常的,编译器要对其设计一些额外代码,如果有了noexcept就可以省去这部分代码。
27. 输入输出符的重载都要作为友元函数,不能作为类的成员函数。另外输出函数不必考虑失败的情况,但输入函数如果存在计算再赋值的情况(如类里有价格、数量、总价),输入只输入价格和数量,总价由函数内部自行计算再赋值,那么就要考虑失败的情况,这时应该将类重新赋值为空。
28. 一般运算符重载作为非成员函数,比如string s; s="121"+s; 这里如果string的加法重载作为成员函数,那么就会报错,因为s="121"+s;相当于调用 "121".soperator+ (s);这显然不对。
operator+一般调用operator+=来实现,而不是反过来。这样可以节省一个临时变量的申请时间和空间。
29. 重载递增递减、下标运算符都要作为成员函数。其中前置++和后置++的区别是靠形参的不同来区别的,后置++的形参有一个int:
1 A& operator++(){ 2 ++feet,++inches; 3 return *this; 4 } 5 A operator++(int){ 6 A temp=*this; 7 ++feet,++inches; 8 return temp; 9 }
另外前置++返回引用,后置++返回右值不用多说了。
可以看到,STL也是这么干的: