C++ primer 笔记 1~7 章
来源:互联网 发布:js数组重排序 编辑:程序博客网 时间:2024/06/13 22:49
page34 无符号数相减
unsigned u = 10 , uu = 42; cout <<u-uu<< endl; cout<<(unsigned)-32<<endl; cout<<pow(2,32)-32<<endl; return 0;
page41 初始化发生了什么?
struct A{ int i = 0; int j = 0; A() = default; A(int k){i = k ; cout<<"k"<<endl; }; A(A& b){cout<<"test2"<<endl;}; A& operator = (const A& b){cout<<"kaobeifuzhi"<<endl;};};int main(){ A a ; A b ; A c = a;//输出:test2 A d={16}; //输出:k return 0;}
page 45
声明语句组成:一个基本数据类型+声明符号列表
声明符命名一个变量,并指定改变量为与基本数据类型相关的某种类型
page53
指针的引用
int* p1 , p2 //p1是指针。p2是整型int *&r;//从右到左结合:&r表明r是个引用,int *&r表明该引用所引用的类型是一个指向整型对象的指针
page54 为什么const限定的对象一定要有初始值
编译器会把const对象换成对应的值,因此,每个文件都必须提供const对象的初始值。在默认情况下,const对象仅仅在一个文件内有效,即我们可以在一个项目的不同文件中定义同名的const对象。
如果想很多文件共享同一个const对象,在不管是声明还是定义,在前面加上extern语句。如果有初值,则即使声明又是定义。没有初值的话,则是声明。
例如,文件a.cpp内:
extern const int a = 250;int b = 90 ;
文件b.cpp内:
#include <iostream>using namespace std;extern const int a ;extern int b ;int main(){ cout<<a<<endl; cout<<b<<endl; return 0;}
命令行
g++ a.cpp b.cpp -o c
但是,如果在a.cpp中,定义a的时候没有extern关键字,则程序将会报错,这是因为const默认情况下只在文件内有效。
page59 constexpr
常量表达式满足两点: 1.值不会改变;2.编译时就会得到结果
例如 int i = 8 ;不是常量表达式,因为i是个变量。
我们知道,const限定的对象,在编译时会将等号右边的值替换它,因此,有如下代码和注释:
int i = 9 ;const int j = 10 ; //j是常量表达式const int k = i ; //k不是常量表达式constexpr int s = j+1 //正确:j是常量表达式,所以j+1也是常量表达式constexpr int q = k+1//错误:k不是常量表达式,因此k+1不是常量表达式
常量表达式的优点是显然的,在编译的时候就已经把它计算出来了,每当程序运行的时候,就不用再去计算它。
C++11之所以引入constexpr限定符,是因为有些复杂系统,我们无法知道它是否是常量表达式,加上该限定符后,就是告诉编译器,如果等号左边是常量表达式,那么就执行,否则,就报错。
常量表达式得到的数据类型叫做字面值类型,指针和引用都可以定义为字面值类型,但注意,字面值类型必须是编译的时候就能计算出来,因此指针和引用所指向的对象必须有固定的地址,或者指针的值是nullptr.只有定义所有函数体之外的变量具有固定地址,因此引用和指针不能指向任何函数体之内的对象。
当然这有例外,如果函数体内可以定义有效范围超出函数体本身的变量,它变可能有固定地址。这种变量叫做 局部静态对象,它在程序第一次执行它时候初始化,直到程序终止时候才被释放。例如下面的语句是正确的:
void test(int i = 0 ){ static int a = i; constexpr int &b = a ;}
page 60 typedef
代码
typedef int (*P)[10] ;P p;cout<<sizeof(*p)<<endl;cout<<10*sizeof(int)<<endl;
page 63 decltype 和 引用
decltype可以返回引用,因此:
int i = 0 ;int &r = i ;decltype(r) rr ;//出错:r是引用类型指针的解引用运算符返回引用类型,因此
int i = 0 ;int *p = &i ;decltype(*p) rr ;//出错:r是引用类型
当decltype内是一个变量时候,它返回该变量的类型。但如果是改变量外还有个括号,则decltype推断它是表达式。变量是可以作为赋值语句左侧的特殊表达式,因此,它将返回引用类型:
int i ;decltype( (i) ) r ; //错误:r是引用类型,需要初值
page 76 补充:关于隐式转换
拷贝初始化的时候,如果被初始化的对象是常量引用,那么,它可以绑定到临时对象上,这个时候,可以通过隐式转换构造临时对象:
struct A{ A() = default; A(int k) { cout<<"test"<<endl; };};int main(){ const A &a = 16 ;//正确,发生隐式转换 A b = 16 ;//错误 const A c = 16 ;//错误 return 0 ;}这里还要强调拷贝初始化和直接初始化的区别:拷贝初始化一般调用拷贝构造函数,是把参数里的变量拷贝到所构造的对象内,必要时还要进行类型转换。直接初始化则使用函数匹配进行初始化。
page 95 注记:下面代码均不输出250.
vector<int> v = {1,2,3} ; auto b = v.begin() , e = v.end(); v.push_back(250); cout<<*(b+3)<<endl; cout<<*e<<endl;
page114 多维数组和范围for循环
本质上,c++没有多维数组,所谓多维数组,是数组的数组
范围for循环中,除了最内层循环外,其它循环应该声明成引用,这是为了避免将数组转化为指针。例如:
int a[10][10][10]; for(auto &x : a ) for(auto &y : x) for(auto &z : y )//改变数组的值,所以最内层也要声明成引用 z = 250; for(auto &x : a ) for(auto y : x)//编译报错,y必须要声明为引用,否则,它被转化成指针,从而导致下一层循环没意义 for(auto z : y )//不改变z,该层可以不声明成引用,但明显声明成引用效率更高 cout<<z<<" "<<flush; cout<<endl;
page120 当一个对象作为左值的时候,使用的是该对象的身份;当一个对象作为右值的时候,使用的是该对象的值。
左值不一定处在赋值号的左边,例如const对象是左值,但不能出现在赋值号的左边。
递增或者递减运算符返回左值:
++i = 9;
page174 异常
关键字 throw 用于抛出异常,用法是 throw + 异常类型对象,例如抛出一个runtime类型的异常:
throw runtime_error("test") ;
注意,runtime对象必须使用一个字符串对它初始化。
try语句可以吞下异常,例如:
try{ cout<<"please input 1: "<<endl; int i ; cin>>i ; if(i != 1) throw runtime_error("you should input 1 !") ; }catch (runtime_error err){ cout<<"catch succeed !"<<endl ; }这里我们使用catch吞下了异常。
page196 如何向函数内传递多维数组??
我们知道,不能像函数传递数组,所有为数组的形参,都会转化为指针,例如,下面的函数声明是等价的:
void g(int a[250]);void g(int* p);void g(int* a[]);传递一个数组,我们需要传递两个参数,指向数组首元素的指针和数组长度。
指针移动的步长是根据指针类型决定的,和指针本身的表达方式和存储方式无关,我们可以像函数内传递一个指向数组的指针。因此,例如我们要传递数组a[10][100] ,则可以这样声明函数
void f( int (p*)[100] , rows) ;通过如下方式调用:
f(a,10);
page198 可变形参的函数
其实 ,也可以用vector实现可变形参函数:
void h(vector<int> v){ for(auto &s : v) cout<<s<<endl;}调用代码:
h({1,2,3});
page205 返回数组的指针或者引用:
int (*func(int a))[10] ;func(int a)表明func是个函数,它调用一个整型形参;
*func(int a)表明,该调用返回类型是一个指针类型;
*func(int a)[10]表明,该指针指向一个具有10个元素的数组;
int *func(int a)[10] 表明,该数组内的类型是整型。
page 208 函数重载
重载不去分顶层const,因此,如下每对的声明都是相同的
void f(const int);void f(int);
void g(int* const ); void g(int*);
常量引用和常量指针是底层const。
page210 作用域内声明的函数,将隐藏外层作用域的同名实体(即使形参不同)。测试代码
#include <iostream>using namespace std;void test(int i ){ cout<<"int"<<endl;}void test(double j ){ cout<<"double"<<endl;}int test(bool b){ cout<<"bool"<<endl;}int main(){ int i ; double d; bool b; cout<<"before"<<endl; test(i); test(d); test(b); cout<<"after"<<endl; void test(bool) ; test(b); test(i);//输出bool,而非int test(d);//输出bool,而非double return 0 ;}
page 212 默认实参
我们可以只在声明中设定默认实参,然而只能设定一次。
page 214 返回类型为constexpr函数
我们允许这种函数在定义时返回非常量,当调用它的时候,编译器会判断它返回的是否是常量表达式,如果不是常量表达式,则报错
内联函数相当于在编译过程内联的展开,因此,内联函数可以在程序中重复定义。但一个源文件中只能定义一次
constexpr函数也可以重复定义。但一个源文件中只能定义一次。
注记:constexpr函数内,只能是一条return语句,不能有其它语句!!!
page230 类
成员函数调用:
a.f()1. this指针:上述语句,隐式将a的地址传给f内的指针this。默认情况下,this是指向非常量的常量指针,因此,默认情况下它不能绑定到常量对象身上。
2.引入const:为了使this能够绑定到常量对象上,在定义成员函数的时候,引入const,代码例子:
const int& f() const {cout<<"const"<<endl;return i ;}当然,这个函数也可以调用非常量版本的对象,但不能改变它:
//以下是类定义代码://....const int& f() const {cout<<"const"<<endl;return i ;} //常量版本//... //以下是main函数以内代码://... A a;//a.f() = 100 ;//出错,因为this指向常量a.f();//...
如果我们想改变i的值,则重载它:
//以下是类定义代码://....const int& f() const {cout<<"const"<<endl;return i ;} //常量版本int& f(){cout<<"non_const"<<endl ; return i ;} //... <pre name="code" class="cpp">//以下是main函数以内代码://... A a;a.f() = 100 ;//正确,调用接受非常量版本a.f();//...
注记:编译顺序:先编译成员,再编译成员函数,因此,成员函数可以调用源文件中定义在它后面的成员。
page235 构造函数
构造函数可以对const对象写值,只有构造函数完成初始化后,const对象才具有“const”属性。
测试代码:
struct A{ A(const int& k ): i(k) {}private: const int i = 10;};注意,对i初始化语句是i(k) 。
一个疑问:
//文件a.cpp内:#include <iostream>using namespace std;struct A{ int i ; void f(){cout<<"hello"<<endl;}};void g();int main(){ A a; a.f(); g(); return 0;}//文件b.cpp内:#include <iostream>using namespace std;struct A{ int i ; void f(){cout<<"hi"<<endl;}};void g(){ A a; a.f();}shell命令:
g++ a.cpp b.cpp -o c结果,输出都是“hello”
page242 友元
在类内声明类外函数为类的友元,使得这个函数能够访问类的私有成员
类内关于友元的声明,只是声明了该函数的访问权限,如果我们想在类外调用它,则还要声明它
声明示例:
//类内//...friend void fr(A a);//...//类外//...//友元函数定义如普通函数:void fr(A a){cout<<a.i<<endl;}//i为a的私有成员//...
mutable 数据成员
如果类A内有一个mutable数据成员,那么不管A的对象是否是const的,该数据成员都可变:
struct B{ mutable int i = 100 ;};//...const B b;b.i = 250; //正确:i是mutable的
类可以只声明,不定义,此时该类是不完全类型,这个时候,可以定义指向这种类型的指针
除了静态成员外,只有类完成定义后,才能创建该类的对象,毕竟,只有类的成员是已知的,编译器才知道分配多少空间,怎样初始化对象。
测试代码:
#include <iostream>using namespace std;class A;A *p;struct A{ int i ; static int j ;};int main(){ A a[2]; p = a; p->i = 1 ; (p+1)->i = 2 ; cout<<a[0].i<<" "<<a[1].i<<endl; return 0;}
注意,上述代码中没有定义static成员,但仍然能够定义和初始化对象。这是因为静态成员之和类类型关联,和对象无直接关系。
page 250 友元再探
不光函数可以定义为类的友元,还可以把其它类,其它类的成员函数定义为友元,类内声明示例:
//声明友元类class A{ //A的定义 friend class B; //A的其它定义}//令成员函数作为友元class A{ //A的定义 friend double C::clear(int) ; //A的其它定义}声明友元的时候,要协调好定义每个类出现的顺序,代码示例:
#include <iostream>using namespace std;class A;//因为定义C过程中成员函数g的形参的类型是A,所以要声明它,但不必定义struct C{ //因为下面A声明了void C::g(A) ,所以要定义类C,但成员函数g没必要定义 void g(A a);};struct A{ friend class B; //这里应该包含了B的声明,所以前面不需要单独对B进行声明 friend void C::g(A);private: int i = 250;};struct B{ void f(A a){cout<<"B: "<<a.i<<endl;}};void C::g(A a){cout<<"C: "<<a.i<<endl;}int main(){ A a; B b; C c; b.f(a); c.g(a); return 0;}
page252 作用域
struct A{ typedef int *P; P p;};P p ; //错误,P离开了它的作用域AA::P q;//正确
page254 查找规则
普通作用域查找规则:
int i = 1; { int j = i ; int i = 2 ; int k = i ; cout<<j<<" "<<i<<endl; }
这个时候,j = 1 ,即外层作用域的i , k = 2 ;
然而,类的查找规则略有不同,这是因为类是先处理声明,直到类成员全部可见后,再处理函数体的。因此,代码
int i = 1 ;struct A{ void f(){cout<<i<<endl;}private: const int i = 2;};int main(){ A a; a.f(); return 0;}结果将输出2.
注记:如果在类外部定义成员函数,如果一个全局函数出现在该成员函数之前,并且出现在类的定义之后,那么,这个全局函数使可以被该成员函数利用的
page264 隐式转换
接受某个实参的构造函数有时也叫做转换构造函数,这是因为它可以实现隐式转换,例如:
struct A{ A() = default; A(const A &rhs): i(rhs.i){cout<<"copy constructor"<<endl;} A(int k): i(k) {cout<<"convert"<<endl;} int i ;};void test(A a){ cout<<a.i<<endl;}在上述定义下,如果我们有如下调用语句:
test(250) ;编译器受限利用250构造一个临时对象,这时它将调用A(int)构造函数,因此它会输出“convert” , 然后,输出a.i,即250.值得注意的是,它并没有调用构造函数A(const A&)
另外还请注意,如果构造函数是非常量引用: A(int&),毕竟,我们不能是一个非常量引用指向临时对象。。。
然而,常量引用是可以滴:A(const int&);
关于explicit,有如下说明
1.如果要阻止隐式转换,则在声明前加上explicit,例如:
explicit A(const A &rhs): i(rhs.i){cout<<"copy constructor"<<endl;}
2.当构造函数有多个形参时,不能执行隐式初始化,因此我们也就没有必要对这种构造函数进行explicit声明
3.隐式转换只能用在直接初始化,拷贝初始化则禁止隐式转换,例如,尽管拷贝构造函数是允许隐式转换的,但如下拷贝初始化语句仍然不能通过编译:
//定义Sales_data类的代码:...Sales_data( const Sales_data&);Sales_data(string);...//定义Sales_data类的对象:string null_bookSales_data a = null_book ; //错误:拷贝构造函数不能隐式转换
- c++primer笔记1
- C++primer学习笔记 第7章
- C++Primer 第7章笔记整理
- 《c++primer》笔记 第1章 开始
- c++primer 1-3章简略笔记
- c++primer第十章笔记
- C++primer第二章笔记
- C++primer第三章笔记
- c++primer笔记(1)
- C++primer学习笔记(1)
- C++Primer学习笔记《1》
- C++Primer学习笔记第七章(7/18)函数
- c++primer 4 学习笔记--第7章
- C++primer plus第7-8章函数笔记
- 《C++Primer》学习笔记(1-5章)
- c++Primer学习笔记(7)--函数
- C++Primer学习笔记(7)
- C++ Primer 笔记7
- yeoman简单环境搭建
- java 中定时任务
- usaco Barn Repair (牛宿舍问题||贪心)
- Redis的键(key)
- CreateThread和_beginthread区别及使用
- C++ primer 笔记 1~7 章
- java web 开发模式
- HDU 1513 Palindrome
- 从零开始学spring-boot(3)-集成logback日志
- 打印对象和toString方法
- Linux 使用 vsftpd 搭建 ftp 服务器
- tomcat版本和jsp版本servlet版本的关系
- C#中怎么查询dataset中的数据,取其一行数据的每列到textbox中
- Xcode8去除控制台多余打印