目录
1.关键字(c++98)
c++(98)的关键字比c语言多了几乎一倍
2.命名空间
c语言无法解决命名冲突的问题,如与库中的函数名称冲突,项目中各成员的区块代码之间冲突
因此引进命名空间的概念,即namespace
int a = 5;
void f1()
{
int a = 0;
printf("%d\n",a);//输出0,默认在局部找
printf("%d\n",::a);//::称为域作用限定符,前面若为空,则默认在全局寻找
}
void f2()
{
int a = 1;
printf("%d\n",a);//输出1
}
int main()
{
printf("%d\n",a);//输出5,现在局部main函数中找,没找到,再到全局找找到a=5
f1();
f2();
return 0;
}
//但是值得注意的是,在f1中无法寻找f2中的a
命名空间 ----命名空间域,只影响使用,不影响生命周期
namespace anode
{
struct node
{
int val;
struct node* next;
struct node* pre;
};
int x = 0;
}//不需要加引号
namespace bnode
{
struct node
{
int val;
struct node* next;
struct node* prev;
};
int x = 0;
}
int main()
{
struct anode::node n1;
struct bnode::node n2;
anode:: x++;
bnode:: x++;
return 0;
}
同时1.在多个文件中同名的命名空间会合并
2.命名空间可以嵌套
namespace node
{
namespace A
{
struct node
{
int val;
struct node* next;
struct node* pre;
};
}
namespace B
{
struct node
{
int val;
struct node* next;
struct node* pre;
};
}
}
int main()
{
struct node::A::node n1;
return 0;
}
而using namespace std ;即为全局展开c++标准库
也可以部分展开,比如 using std::cout;
3.缺省值
(1)全缺省
void print(int a = 1, int b = 2, int c = 3)
{
printf("%d\n",a);
printf("%d\n",b);
printf("%d\n",c);
}
//支持以下几种方式
int main()
{
print(4,5,6);
print(4,5);
print(4);
print();
return 0;
}
只能从右往左依次缺省,若传值则使用传入的值,否则使用缺省值
(2)半缺省
void print(int , int b, int c = 3)
{
printf("%d\n",a);
printf("%d\n",b);
printf("%d\n",c);
}
void print(int a , int b = 2, int c = 3)
{
printf("%d\n",a);
printf("%d\n",b);
printf("%d\n",c);
}
缺省值也是只能从右往左依次缺省
ps 如果声明和定义分离,那么缺省值只能在声明中给
4.输入输出
c++使用
>>流提取
<< 流插入
endl等效于 '\n'
int main()
{
cout << "hello world" << endl;
int a;
cin >> a;
cout << a << endl;
int * arr = (int *) malloc(sizeof(int)*5);
for(int i = 0; i < 5; i++)
{
cin >> arr[i];
}
for(int i = 0; i < 5; i++)
{
cout << arr[i] << endl;
}
return 0;;
}
cin 与 cout 会自动识别类型
5.函数重载
函数参数的类型,数量,顺序不同均可构成函数重载
void sum (int x, int y)
{
;
}
void sum (int x, int y, int z)
{
;
}
void sum(int x, char y)
{
;
}
void sum(char x, int y)
{
;
}
并且与返回值无关,返回值的不同不构成函数重载
在各个编译器上,编译器会对函数名称进行修饰,不同编译器修饰规则会不同
以下是一种简单的示例
比如 _zsumii _zsumic (i 为int 的首字母, c为char的首字母)
编译器会识别修饰之后的函数名
6.引用
简单来说就是给变量取别名,
1.一个变量可以有多个别名,变量的别名也可以再取别名
2.引用必须在初始化的时候就赋值
int i = 0;
int& j = i;
int& k = i;
int& l = j;
3.引用一旦引用一个实体,就不能再引用其他实体
例如:
int a = 10;
int b = 20;
int& ref = a;
ref = b;//这里的赋值是错误的
可以用于规避二级指针带来的复杂性,使代码更简洁
struct list
{
int data;
srtuct list* next;
};
void buylist(list*& node, int x)
{
list* tmp = (list*)malloc(sizeof(list));
if(tmp == nullptr)
{
perror("failed :");
return;
}
if(node == nullptr)
{
node = tmp;
}
}
知识补充
int Count1()
{
int a = 0;
a++;
return a;
}
int Count2()
{
static int b = 0;
b++;
return b;
}
int main()
{
int ret1 = Count1();
int ret2 = Count2();
return 0;
}
这两个函数属于传值返回。无论是第一个还是第二个函数,即不管变量出作用域之后是否销毁,都会产生一个临时变量来充当返回值。如果比较小就存在寄存器中,大的话会提前在上一层函数中开好空间0。
而如果使用传引用返回,返回的是其别名,那么就不会产生拷贝的过程,并且可以修改返回对象
int& Count3()
{
static int c = 0;
c++;
return c;
}
int main()
{
int ret3 = Count3();
Count3() = 5;
return 0;
}
使用传引用返回的条件是出了作用域,这个变量还在,就可以使用传引用返回
临时变量具有常性,不能修改(可以理解为被const修饰)
另外,指针进行初始化和赋值以及引用进行初始化的时候,权限可以缩小,但是不能放大,因为对于指针来说,两个变量里的指针指向的是同一块空间,而对于引用来说,一个变量改变可以直接影响另一个
下面是错误代码示例
const int a = 2;
int & b = a;
const int *c = & a;
int* & d = c;
这里是正确代码示例
const int a = 2;
const int & b = a;
const int *c = & a;
const int* & d = c;
int a = 2;
const int & b = a;
int *c = & a;
const int* & d = c;
但是其它类型,比如说int,就没有这样的限制,因为它只是进行一个拷贝
此外,类型转换也会产生临时变量
int i = 0;
double & j = i; //这个编译不能通过
const double & k = i; //这个可以编译通过
i类型转换产生一个临时变量,k是这个临时变量的别名
引用比指针要更安全一些。
并且引用的底层是指针,虽然在语法上引用不会再去开空间,但是实际上是会的。
7.内联函数
使用宏函数不建立栈帧
c++推荐用const ,enum替代宏常量
inline替代宏函数
#define ADD(x, y)((a) + (b))//写一个ADD的宏函数
宏的缺点:
1.不能调试
2.没有类型安全的检查
3有些场景下非常复杂,容易出错
内联函数也不建立栈帧,但是在默认的debug版本不起作用,因为那样就不能调试了,release下会起作用,编译的时候会在调用内联函数的地方展开
注意点
1.内联函数是以“空间”换时间的做法,这个空间指的是指令。
比如说一个add函数的指令是十行,调用一千次,指令就是1000+10行,每次调用都是先call,再jmp跳转
而如果它是内联函数,那么指令大约是10*1000行(粗略),因为每次调用都会把函数展开
2.内联函数主要用于程序短,调用频繁的函数
3.内联函数不建议声明和定义分离,因为内联展开了,就没有函数地址了,链接就会找不到
最后inline只是一种建议,使用inline关键字之后不一定该函数就一定会成为内联函数,编译器会自行进行判断
8.auto
可以自动识别类型,简化代码
int a = 1;
auto b = a;
map <string,string> dict;
auto dit = dict.begin();
可以用于范围for遍历数组
int arr[] = {1,2,3,4};
for(auto a : arr)
{
cout << e << " ";
}
cout << endl;
但是这个语法是把数组中的数据赋值给a,a只是数组中值的拷贝。
如果想要改变数组的值可以使用引用
for (auto& a : arr)
{
a = a * 2;
}
但是这个仅仅可以用于数组,如下代码是编不过的,下面函数中传的实际上是地址
void print(int arr【】)
{
for(auto a : arr)
cout << a << " ";
}
auto在同一行声明多个变量时,这些变量必须要类型相同,否则编译器会报错,因为编译器实际上只对第一个类型进行推导,然后用推导出来的类型定义其它变量。
auto a = 1, b = 2;
auto 不能作为函数的参数
void add(auto a)
{
;
}
auto 也不能用来声明数组
auto arr【】 = {1,2,3};
9.nullptr(c++11)
可以理解为使用void *强转的0值。因为在c++中NULL的值就是一个int类型的0.