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 或 Return3、列表初始化(P39)
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)
iostream | istream, wistream 从流读取数据 ostream, wostream 向流写入数据 iostream, wiostream 读写流 |
fstream | ifstream, wifstream 从文件读取数据 ofstream, wofstream 向文件写入数据 fstream, wfstream 读写文件 |
sstream | istringstram, wistringstream 从string读取数据 ostringstream, wostringstream 向string写入数据 stringstream, wstringstream 读写string |
- IO对象不能拷贝和赋值
- 不能将形参或返回值设为流对象
- IO操作的函数通常以引用方式传递和返回流
- 读写一个IO对象会改变其状态,所以传递和返回的引用不能是const
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执行时变量时存在的