C++ Primer 读书笔记

版权声明: https://blog.csdn.net/lihaidong1991/article/details/79441653

1、main函数的返回值是int类型,不能是void?

main函数的返回值应该定义为int类型,C和C++标准中都是这样规定的.

对于”void main(void);”这种错误形式,虽然在一些编译器中可以通过编译(如vc6),但并非所有的编译器都支持,因为标准中从来没有定义过这种形式. g++3.2编译器中,如果返回值类型不是int类型,根本不会通过编译;则gcc3.2 编译器则会发出警告.

所以,如果想让你的程序有很好的可移植性,请一定要使用int main.

main函数几种形式如下: 
C语言中: 
int main(void); 
int main(int argc,char* argv[]); 
或者 int main(int argc, char** argv); 
C++语言中: 
int main(); 
int main(int argc,char* argv[]);

在main函数的返回值必须是int类型的前提下,如果main函数的最后没有写return语句,会如何?

C99和C++98规定编译器要自动在生成的目标文件中加入”return 0;”, 表示程序正常退出.写程序好的习惯是自己在main函数的最后加上return语句,因为并不是所有的编译器都支持这一特性. 
例如: 
vc6编译器不会在目标文件中自动添加return 0语句. 
gcc3.2(Linux下的C编译器)支持这个特性. 
g++3.2(Linux下的C++编译器)支持这个特性.

总结: 
综上所述,由于void main类型不在规定中,而且不同的编译器对main函数的返回类型的处理不一样,以及不同编译器不一定会在目标文件中自动加入return语句这3点,为了让我们的程序有很好的可移植性,写main函数时需要按照以下形式: 

int main() 
{ 
    return 0; 
}

2、键盘输入文件结束符(P14)

windows: Ctrl + Z -> Entre 或 Return
unix: Ctrl + D


3、列表初始化(P39)

C++11新标准建议采用列表初始化的方式初始化变量,可以避免初始值存在丢失信息的风险。
long double d = 3.14;
int a{d}; // 错误:转换未执行,存在丢失信息风险,编译报错
int b(d); // 正确,转换成功,但丢失部分值


4、变量的初始化(P41)

  • 定义在函数体内的内置类型未初始化,其值未定义。类的对象未初始化,由其值确定。
  • 想声明变量但不定义,在之前加关键字 extern,而且不要显示定义
  • 加了extern却又显示定义,还是属于定义
  • 一个变量只能定义一次,但可以多次声明
  • extern int i; // 声明 i
    int j; // 定义 j
    extern double k = 2.0; // 定义 k


5引用(P45~P46)

  • 引用格式:&d,d为变量名
  • 引用并非对象,只是变量的别名
  • 不能定义引用的引用
  • 引用必须被初始化
  • 引用只能绑定到对象上,不能绑定到常量上
  • 引用的绑定对象必须类型严格一致
int &a; // error,必须初始化
int &i = 3.14; // error, 不能引用常量

double b = 2.0;
int &c = b; // error, 类型不一致

double &d = b;
double &e = d; // error, 不能定义引用的引用


6、const(P53~P55)

默认状态下,const对象仅在文件内有效,想在多个文件中共享,需在const变量的声明和定义出添加extern关键字。

// file.cpp 定义并初始化常量,可以被其他文件使用
extern const int bufSize = fcn();

// file.h
extern const int bufSize; //与file.cpp中是同一个


7、decltype(P63)

  • decltype(*p)得到引用,必须初始化
  • decltype((a)) 得到的永远是引用,必须初始化
  • decltype(a), a为引用时才得到引用


8、string(P74~P86)

  • 头文件 <string>
  • 直接初始化和拷贝初始化
string s1 = "hello"; //直接初始化
string s2 = s1; // 拷贝初始化
  • size() 返回值为 size_type 类型
  • 读取string
  • cin>>s; // 遇到空格为止,忽视前面的空格
    getline(cin, s); //读取整行,行后的换行符被丢弃
  • string 加法(+)两侧必须有一个是string类型,字符串字面值常量不是string类型
string s1 = "hello"; //直接初始化
string s2 = s1 + ", "; //
string s3 = ", " + s1;
string s4 = "hello" + ", "; // error


9、求值顺序(P123)

对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一对象,将产生未定义结果

int i = 0;
cout << i << "  "<< ++i <<endl; // 结果未定义
auto beg = s.begin();
while (beg != s.end())
    *beg = toupper(*beg++);  //  error

*beg = toupper(*beg); // 先求左侧值
*(beg + 1) = toupper(*beg); // 先求右侧值

4种明确指定运算对象的求值顺序的运算符: &&   ||   ?;   ,


10、递增递减(P132)

优先使用前置版本(++i, --i),因为后置版本(i++, i--)需要将原始值存储下来,有一定的资源浪费。



11、运算符优先级表(P147)

优先级 操作符 描述 例子 结合性
1 ()
[]
->
.
::
++
--
调节优先级的括号操作符
数组下标访问操作符
通过指向对象的指针访问成员的操作符
通过对象本身访问成员的操作符
作用域操作符
后置自增操作符
后置自减操作符
(a + b) / 4;
array[4] = 2;
ptr->age = 34;
obj.age = 34;
Class::age = 2;
for( i = 0; i < 10; i++ ) ...
for( i = 10; i > 0; i-- ) ...
从左到右
2 !
~
++
--
-
+
*
&
(type)
sizeof
逻辑取反操作符
按位取反(按位取补) 
前置自增操作符
前置自减操作符
一元取负操作符
一元取正操作符
解引用操作符
取地址操作符
类型转换操作符
返回对象占用的字节数操作符
if( !done ) ...
flags = ~flags;
for( i = 0; i < 10; ++i ) ...
for( i = 10; i > 0; --i ) ...
int i = -1;
int i = +1;
data = *ptr;
address = &obj;
int i = (int) floatNum;
int size = sizeof(floatNum);
从右到左
3 ->*
.*
在指针上通过指向成员的指针访问成员的操作符
在对象上通过指向成员的指针访问成员的操作符
ptr->*var = 24;
obj.*var = 24;
从左到右
4 *
/
%
乘法操作符
除法操作符
取余数操作符
int i = 2 * 4;
float f = 10 / 3;
int rem = 4 % 3;
从左到右
5 +
-
加法操作符
减法操作符
int i = 2 + 3;
int i = 5 - 1;
从左到右
6 <<
>>
按位左移操作符
按位右移操作符
int flags = 33 << 1;
int flags = 33 >> 1;
从左到右
7 <
<=
>
>=
小于比较操作符
小于或等于比较操作符
大于比较操作符
大于或等于比较操作符
if( i < 42 ) ...
if( i <= 42 ) ...
if( i > 42 ) ...
if( i >= 42 ) ...
从左到右
8 ==
!=
等于比较操作符
不等于比较操作符
if( i == 42 ) ...
if( i != 42 ) ...
从左到右
9 & 按位与操作符 flags = flags & 42; 从左到右
10 ^ 按位异或操作符 flags = flags ^ 42; 从左到右
11 | 按位或操作符 flags = flags | 42; 从左到右
12 && 逻辑与操作符 if( conditionA && conditionB ) ... 从左到右
13 || 逻辑或操作符 if( conditionA || conditionB ) ... 从左到右
14 ? : 三元条件操作符 int i = (a > b) ? a : b; 从右到左
15 =
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
赋值操作符
复合赋值操作符(加法)
复合赋值操作符(减法)
复合赋值操作符(乘法)
复合赋值操作符(除法)
复合赋值操作符(取余)
复合赋值操作符(按位与)
复合赋值操作符(按位异或)
复合赋值操作符(按位或)
复合赋值操作符(按位左移)
复合赋值操作符(按位右移)
int a = b;
a += 3;
b -= 4;
a *= 5;
a /= 2;
a %= 3;
flags &= new_flags;
flags ^= new_flags;
flags |= new_flags;
flags <<= 2;
flags >>= 2;
从右到左
16 , 逗号操作符 for( i = 0, j = 0; i < 10; i++, j++ ) ... 从左到右


12、数组形参(P193)

  • 不允许拷贝数组
  • 使用数组会转换为指针


13、指针函数与函数指针(P223)

指针函数:

指针函数是指带指针的函数,即本质是一个函数,函数返回类型是某一类型的指针

类型标识符 *函数名(参数表)

int *f(x,y);

首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。

表示:

float *fun();
float *p;
p = fun(a);


当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。
格式:
类型说明符 * 函数名(参数)
当然了,由于返回的是一个地址,所以类型说明符一般都是int。

例如:

int *GetDate();
int * aaa(int,int);

 

函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。

复制代码
 1 int * GetDate(int wk,int dy);
 2 main()
 3 {
 4   int wk,dy;
 5   do{
 6   printf(Enter week(1-5)day(1-7)\n);
 7   scanf(%d%d,&wk,&dy);
 8   }
 9   while(wk<1||wk>5||dy<1||dy>7);
10   printf(%d\n,*GetDate(wk,dy));
11 }
12 
13 int * GetDate(int wk,int dy)
14 {
15   static int calendar[5][7]=
16   {
17     {1,2,3,4,5,6,7},
18     {8,9,10,11,12,13,14},
19     {15,16,17,18,19,20,21},
20     {22,23,24,25,26,27,28},
21     {29,30,31,-1}
22   };
23   return &calendar[wk-1][dy-1];
24 }
复制代码

程序应该是很好理解的,子函数返回的是数组某元素的地址。输出的是这个地址里的值。

 

函数指针

函数指针是指向函数的指针变量本质是一个指针变量。

    int (*f) (int x); /*声明一个函数指针 */

   f=func; /* 将func函数的首地址赋给指针f */

指向函数的指针包含了函数的地址的入口地址,可以通过它来调用函数。声明格式如下:
类型说明符 (*函数名)   (参数)
其实这里不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明笔削和它指向函数的声明保持一致。
指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。
例如:

void (*fptr)();

把函数的地址赋值给函数指针,可以采用下面两种形式:

fptr=&Function;
fptr=Function;

取地址运算符&不是必需的,因为单单一个函数标识符就标号表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
可以采用如下两种方式来通过指针调用函数:

x=(*fptr)();
x=fptr();

第二种格式看上去和函数调用无异。但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。

下面举一个例子:

复制代码
 1 void (*funcp)();
 2 void FileFunc(),EditFunc();
 3 
 4 main()
 5 {
 6   funcp=FileFunc;
 7   (*funcp)();
 8   funcp=EditFunc;
 9   (*funcp)();
10 }
11 
12 void FileFunc()
13 {
14   printf(FileFunc\n);
15 }
16 
17 void EditFunc()
18 {
19   printf(EditFunc\n);
20 }
复制代码

 

程序输出为:

FileFunc
EditFunc


14、默认构造函数(P236)

下面情况不会合成或不能依赖默认构造函数

  • 声明了其他构造函数
  • 合成的默认构造函数可能有错,如有块中定义的内置类型,值是未定义的
  • 类中包含没有默认构造函数的类

15、,类默认函数(P239)

    构造函数、拷贝构造函数、赋值函数、析构函数


16、class  & struct(P240)

    唯一的区别就是默认的访问权限


17、列表初始化(P258)

  • 如果成员函数有 const 或 引用,必须用默认初始化
  • 列表初始化效率高于赋值初始化
  • 列表初始化的顺序只由成员的声明顺序决定

18、IO类(P278)

IO库类型和头文件
iostream

istream, wistream   从流读取数据

ostream, wostream 向流写入数据

iostream, wiostream 读写流

fstream

ifstream, wifstream   从文件读取数据

ofstream, wofstream 向文件写入数据

fstream, wfstream  读写文件

sstream

istringstram, wistringstream   从string读取数据

ostringstream, wostringstream 向string写入数据

stringstream, wstringstream    读写string




19、IO流对象 (P279)

  • IO对象不能拷贝和赋值
  • 不能将形参或返回值设为流对象
  • IO操作的函数通常以引用方式传递和返回流
  • 读写一个IO对象会改变其状态,所以传递和返回的引用不能是const

20、列表初始化 (P258)
ofstream out1, out2;
out1 = out2;  // error, 不能赋值
ofstream print(ofstream); // error, 不能初始化流参数
out1 = print(out2); // error, 不能拷贝


21、emplace(P308)

emplace操作是C++11新特性,新引入的的三个成员emlace_front、empace 和 emplace_back,这些操作构造而不是拷贝元素到容器中,这些操作分别对应push_front、insert 和push_back,允许我们将元素放在容器头部、一个指定的位置和容器尾部。

两者的区别 
当调用insert时,我们将元素类型的对象传递给insert,元素的对象被拷贝到容器中,而当我们使用emplace时,我们将参数传递元素类型的构造函,emplace使用这些参数在容器管理的内存空间中直接构造元素。

例子 
假定d是一个Date类型的容器。 
//使用三个参数的Date构造函数,在容器管理的内存空间中构造新元素。 
d.emplace_back(“2016”,”05”,”26”);

//错误,push_back没有这种用法 
d.push_back(“2016”,”05”,”26”);

//push_back()创建一个临时对象,然后将临时对象拷贝到容器中 
d.push_back(Date(“2016”,”05”,”26”));

通过例子发现,使用C++11新特性emplace向容器中添加新元素,在容器管理的内存空间中构造新元素,与insert相比,省去了构造临时对象,减少了内存开销。

Note 

emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配。


22、lambda的捕获(P350)

// 值捕获
void f1()
{
    size_t v1 = 42;
    auto f = [v1] { return v1;};
    v1 = 0;
    auto j = f(); // 42
}

// 引用捕获
void f2()
{
    size_t v1 = 42;
    auto f = [&v1] { return v1;};
    v1 = 0;
    auto j = f(); // 0
}

// 可变lambda
void f3()
{
    size_t v1 = 42;
    auto f = [&v1]() mutable { return ++v1;};
    v1 = 0;
    auto j = f(); // 43
}

当引用方式捕获一个变量时,必须保证在lambda执行时变量时存在的


猜你喜欢

转载自blog.csdn.net/lihaidong1991/article/details/79441653