众所周知,c++是一门面向对象的语言,不同于c的面向过程。在学习时,首先我们要懂得一些基础入门知识。
目录:
命名空间
缺省函数
函数重载
extern “C”
引用&
内联函数inline
auto关键字(c++11)
范围for循环(c++11)
nullptr关键字(c++11)
命名空间
- 在c/c++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称都将存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是解决这种问题的。
- c++有一套自己的命名空间std,很多东西的使用都离不开它。
在编写c++代码时,常见的写法如下:
#include<iostream>
using namespace std;
但是有时候我们不想将std全部展开,只想使用其中一部分的时候有两种方法:
#include <iostream>
using std::cout;
using std::endl;
int main()
{
cout << "Hello World" << endl;
return 0;
}
#include <iostream>
int main()
{
std::cout << "Hello World" << std::endl;
return 0;
}
缺省函数
缺省函数是声明或定义函数时给函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。在调用缺省函数时,必须从左往右连续的传参,不能空缺。
全缺省函数:缺省所有参数
半缺省函数:缺省部分参数,但是必须从右向左连续缺省
#include <iostream>
using std::cout;
using std::endl;
void Func(int a, int b = 1, int c = 2)
{
cout << a << b << c << endl;
}
int main()
{
Func(0);
return 0;
}
函数重载
函数重载可以理解为一词多义,它允许定义多个功能相似的同名函数,但是参数(个数、顺序、类型)必须不同。
只是函数的返回值不同,不能构成重载。
#include <iostream>
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
void Func(int a, char b)
{}
void Func(char b, int a)
{}
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
小问题:为什么C++支持函数重载?C为什么不支持?
- c++对函数名字进行了修饰,在g++编译器中,函数名被修饰后为(_Z+函数长度+函数名字+类型首字母)。
- 在Linux中,采用gcc编译完成后,函数名字的修饰没有发生改变;但是在采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
extern "C"
要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译.
引用&
相当于对已知变量起了一个别名,共用同一块地址空间。
在定义的时候必须初始化,并且需要考虑权限的放大缩小问题。
在定义函数时也可以使用引用,函数返回时出了作用域,如果对象还在,可以使用引用;如果不在必须使用传值返回。
#include <iostream>
int main()
{
int a = 1;
double b = a; //隐士类型转换
double& ra = a; //错误
//a在赋值给ra时,会创建一个临时常量tmp
//tmp = a; ra = tmp;
//ra是a的别名可读可写,而tmp是只读的,ra = tmp 会放大权限
const double& ra = a; //正确
return 0;
}
小问题:引用和指针的区别?
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能在引用其它实体,而指针可以在任何时候指向任何一个同类型的实体
- 没有NULL引用,但是有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显示解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
内联函数inline
- 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
- inline是一种以空间换时间的做法,省去调用函数额外开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
- inline对于编译器只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略掉内联。
小问题:宏的优缺点?
- 优点:增强代码的复用性;提高性能
- 缺点:不方便调试宏(因为预编译阶段进行了替换);导致代码可读性差,可维护性差,容易误用;没有类型安全的检查
小问题:C++有哪些技术替代宏?
- 常量定义:换用const
- 函数定义:换用内联函数
auto关键字(c++11)
- 用auto声明指针类型时,用 auto 和 auto* 没有任何区别,但用auto声明引用类型时则必须加&
- auto不能作为函数的参数,也不能直接用来声明数组
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int a = 1;
auto b = a; //b的类型是根据a的类型推导出来的
cout << typeid(a).name() << endl; //int
cout << typeid(b).name() << endl; //int
auto x = &a;
auto* y = &a;
auto& z = a; //引用
cout << typeid(x).name() << endl; //int*
cout << typeid(y).name() << endl; //int*
cout << typeid(z).name() << endl; //int
return 0;
}
范围for循环(c++11)
范围for循环本质上是通过迭代器实现的
使用条件:
- for循环迭代的范围必须是确定的:对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围
- 迭代的对象要实现++和==的操作
- 遍历时如果想要更改元素的值,则必须在auto后面加上引用
#include <iostream>
using std::cout;
using std::endl;
void Func(int arr[])
{
for (auto e : arr) //错误,数组传参时会退化成指针
{
cout << e << " ";
}
cout << endl;
}
int main()
{
int arr[] = { 9, 5, 2, 7 };
//Func(arr);
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << " ";
}
cout << endl;
return 0;
}
nullptr关键字(c++11)
在c中:
- NULL实际是一个宏,在传统的C头文件(stddef.h)中
- NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量
如下程序本意是想通过 Func(NULL) 调用指针版本的 Func(int*) 函数,但是由于NULL被定义成0,因此与程序的初衷相悖
#include <iostream>
using std::cout;
using std::endl;
void Func(int n)
{
cout << "int" << endl;
}
void Func(int* p)
{
cout << "int*" << endl;
}
int main()
{
Func(0); //int
Func(NULL); //int
Func(nullptr); //int*
return 0;
}
在C++11中:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。