C++总计63个关键字,C语言32个关键字
注: 等之后学到相应的关键字时再进行具体的讲解
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
c语言项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入namespace就是为了更好的解决这样的问题。
#include <stdio.h>
#include <stdlib.h>
int rand = 0;
//命名冲突
//我们和库,我们之间
int main()
{
//编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”
printf("%d\n", rand);
return 0;
}
如果局部域和全局域中有同名变量,如何使用全局域中的变量呢?
#include <stdio.h>
int a = 0;
int main()
{
int a = 1;
printf("%d\n", a);
printf("%d\n", ::a);//::是域作用限定符
return 0;
}
#include <stdio.h>
#include <stdlib.h>
//命名空间域
namespace bit
{
int rand = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
namespace fz
{
int rand = 0;
int Add(int left, int right)
{
return (left + right) * 10;
}
}
//用变量/函数...
//编译默认查找
//a、当前局部域
//b、全局域找
//不会到其他命名空间中去找
int main()
{
printf("%p\n", rand);
printf("%d\n", bit::rand);
printf("%d\n", bit::Add(1, 2));
printf("%d\n", fz::Add(1, 2));
struct bit::Node node;
return 0;
}
#include <stdio.h>
#include <stdlib.h>
//命名空间域
namespace bit
{
namespace fz
{
int rand = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
}
namespace fz
{
int rand = 0;
int Add(int left, int right)
{
return (left + right) * 10;
}
}
//用变量/函数...
//编译默认查找
//a、当前局部域
//b、全局域找
//不会到其他命名空间中去找
int main()
{
printf("%p\n", rand);
printf("%d\n", bit::fz::rand);
printf("%d\n", bit::fz::Add(1, 2));
printf("%d\n", fz::Add(1, 2));
struct bit::fz::Node node;
return 0;
}
这里我们借用一下之前栈和队列的文件来演示(文件中的具体内容就不展示了):
//Stack.h
namespace bit
{
//...
}
//Stack.cpp
#include "Stack.h"
namespace bit
{
//...
}
//Queue.h
namespace bit
{
//...
}
//Queue.cpp
#include "Queue.h"
namespace bit
{
//...
}
编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找,所以我们要使用命名空间中定义的变量/函数,有三种方式:
#include "Queue.h"
#include "Stack.h"
struct Stack
{
//...
};
struct Queue
{
//...
};
int main()
{
bit::ST st;
bit::STInit(&st);
return 0;
}
#include "Queue.h"
#include "Stack.h"
//展开命名空间
using namespace bit;
//编译默认查找(全局域和展开的命名空间的查找是同一优先级)
//a、当前局部域
//b、全局域找
//b、到展开的命名空间中查找
int main()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
return 0;
}
但是一旦全局域和展开的命名空间域出现同名的情况;或者展开了多个命名空间域且出现同名的情况,编译器就会不明确了。代码如下:
#include "Queue.h"
#include "Stack.h"
typedef struct Stack
{
}ST;
//展开命名空间
using namespace bit;
int main()
{
ST st;//err
return 0;
}
#include "Queue.h"
#include "Stack.h"
namespace gsm
{
typedef struct Stack
{
}ST;
}
//展开命名空间
using namespace bit;
using namespace gsm;
int main()
{
ST st;//err
return 0;
}
但是如果是在局部域里定义了一个变量,再在命名空间域里定义一个同名的变量,展开命名空间域,默认找到的是局部域里的变量:这是因为局部变量作用域的优先级最高,就会被优先考虑。
#include "Queue.h"
#include "Stack.h"
//指定展开某一个
using bit::ST;
int main()
{
ST st;
return 0;
}
#include <iostream>
int main()
{
int i = 0;
double j = 1.11;
// << 流插入
//自动识别类型
std::cout << i << " " << j << '\n' << std::endl;//endl 是换行的意思
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int i = 0;
double j = 1.11;
// << 流插入
//自动识别类型
cout << i << " " << j << '\n' << endl;
return 0;
}
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int i = 0;
double j = 1.11;
// << 流插入
//自动识别类型
cout << i << " " << j << '\n' << endl;
return 0;
}
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int i = 0;
double j = 1.11;
// >> 流提取
cout << i << " " << j << endl;
std::cin >> i >> j;
cout << i << " " << j << endl;
return 0;
}
#include <iostream>
using namespace std;
//缺省参数
void Func(int a = 1)
{
cout << a << endl;
}
int main()
{
Func(2);
Func();
return 0;
}
#include <iostream>
using namespace std;
//全缺省
void F2(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
F2(1, 2, 3);
F2(1, 2);
F2(1);
F2();
return 0;
}
#include <iostream>
using namespace std;
//半缺省,从右往左缺省
void F3(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
F3(1);
F3(1, 2);
F3(1, 2, 3);
return 0;
}
我们可以举一个例子来看一下缺省参数的用途:
就拿我们之前写的栈的初始化来说,我们统一把容量定义为0,那么如果我们一上来就要插入10000个数据,就要扩容很多次,扩容的消耗是很大的;C语言的解决方法就是定义一个宏N,初始化的时候就开N大小的空间,比如这里N = 10000;但是如果后面还要再定义一个栈,只要插入10个数据,那么这里初始化开10000个空间就浪费了。因此,我们可以在初始化的时候再给一个缺省参数,如果你明确知道要开多大的空间,就传具体值进去,如果不知道要开多大空间,就不用传值进去,函数就默认使用缺省值。
//Stack.h
namespace bit
{
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n = 4);
}
//Stack.cpp
#include "Stack.h"
namespace bit
{
void STInit(ST* ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
}
//Test.cpp
#include <iostream>
using namespace std;
#include "Stack.h"
int main()
{
bit::ST st1;
bit::STInit(&st1, 10000);
for (int i = 0; i < 10000; i++)
{
bit::STPush(&st1, i);
}
bit::ST st2;
bit::STInit(&st2, 10);
for (int i = 0; i < 10; i++)
{
bit::STPush(&st2, i);
}
bit::ST st3;
bit::STInit(&st3);
return 0;
}
C++支持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同或者类型顺序不同。这样C++函数调用就表现出了多态行为,使用更灵活。C语言是不支持同一作用域中出现同名函数的。
类型不同:
#include <iostream>
using namespace std;
void Swap(int* pa, int* pb)
{
cout << "void Swap(int* pa, int* pb)" << endl;
}
void Swap(double* pa, double* pb)
{
cout << "void Swap(double* pa, double* pb)" << endl;
}
int main()
{
int a = 0, b = 1;
double c = 0.1, d = 1.1;
Swap(&a, &b);
Swap(&c, &d);
return 0;
}
#include <iostream>
using namespace std;
//不同作用域 可以同名
namespace bit1
{
void Swap(int* pa, int* pb)
{
cout << "void Swap(int* pa, int* pb)" << endl;
}
}
namespace bit2
{
void Swap(int* px, int* py)
{
cout << "void Swap(int* pa, int* pb)" << endl;
}
}
//同一作用域 可以同名,满足重载规则
void Swap(double* pa, double* pb)
{
cout << "void Swap(double* pa, double* pb)" << endl;
}
using namespace bit1;
using namespace bit2;
//他们俩依旧是ok,不是重载关系(展开命名空间并不是把它们都放在全局域里,他们都还是在各自的域里,不满足重载的条件)
int main()
{
int a = 0, b = 1;
double c = 0.1, d = 1.1;
//调用歧义
//Swap(&a, &b);
Swap(&c, &d);
return 0;
}
参数个数不同:
#include <iostream>
using namespace std;
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(1);
return 0;
}
类型顺序不同:
#include <iostream>
using namespace std;
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main()
{
f(1, 'a');
f('a', 1);
return 0;
}
返回值不同不能作为重载条件,因为调用时也无法区分:
//err
#include <iostream>
using namespace std;
void f()
{
cout << "f()" << endl;
}
int f()
{
cout << "f()" << endl;
return 0;
}
int main()
{
f();
int ret = f();
return 0;
}
下面两个函数构成重载,但是f()调用时,会报错,存在歧义,编译器不知道调用谁:
#include <iostream>
using namespace std;
void f()
{
cout << "f()" << endl;
}
void f(int a = 10)
{
cout << "f(int a)" << endl;
}
为什么C语言不支持重载,C++支持?C++怎么支持的?
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:水浒传中李逵,宋江叫"铁牛",江湖上人称"黑旋风";林冲,外号豹子头。
类型& 引用别名 = 引用对象;
C++中为了避免引入太多的运算符,会复用C语言的一些符号,比如前面的 << 和 >>,这里引用也和取 地址使用了同一个符号&,大家注意使用方法角度区分就可以。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
//引用:b是a的别名
int& b = a;
int& c = a;
int& d = b;
++d;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int x = 0;
int* p1 = &x;
//指针变量取别名
int*& pr = p1;
pr = NULL;
return 0;
}
int main()
{
//int& a;//err
int x = 0;
int& y = x;
int z = 1;
//y变成z的别名呢? err
//还是z赋值给y
y = z;
return 0;
}
#include <iostream>
using namespace std;
#include <time.h>
struct A
{
int a[10000];
};
void TestFunc1(A a)
{
}
void TestFunc2(A& a)
{
}
void TestRefAndValue()
{
A a;
//以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
{
TestFunc1(a);
}
size_t end1 = clock();
//以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
{
TestFunc2(a);
}
size_t end2 = clock();
//分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
#include <iostream>
using namespace std;
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 0, y = 1;
Swap(x, y);
cout << x << ":" << y << endl;
return 0;
}
#include <iostream>
using namespace std;
typedef struct SeqList
{
int* a;
//...
}SLT;
void SeqPushBack(SLT& sl, int x)
{
}
int main()
{
struct SeqList s;
SeqPushBack(s, 1);
return 0;
}
#include <iostream>
using namespace std;
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, *PNode;
//typedef struct ListNode* PNode
//void ListPushBack(LTNode** pphead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{
if (NULL == phead)
{
//phead = newnode;
}
}
int main()
{
LTNode* plist = NULL;
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
return 0;
}
int main()
{
//权限的平移
int x = 0;
int& y = x;
//权限的缩小,可以
const int& z = x;
//z++; //不可以
y++;//会影响z
//权限的放大,不可以
//m只读
//n变成我的别名,n的权限是可读可写
const int m = 0;
//int& n = m;
const int& n = m;
//可以,不是权限的放大
//m拷贝给p,p的修改不影响m
int p = m;
//权限的放大
//p1可以修改,*p1不可以,const修饰的是*p1
//const在*之前修饰的是*p1;在*之后修饰的是p1
const int* p1 = &m;
//p1++;
//int* p2 = p1;//不可以
const int* p2 = p1;
//权限的缩小
int* p3 = &x;
const int* p4 = p3;
return 0;
}
int main()
{
//权限可以平移/缩小 不能放大
double d = 12.34;
//类型转换
int i = d;
const int& r1 = d;
int x = 0, y = 1;
const int& r2 = x + y;
return 0;
}
C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功 能有重叠性,但是各有自己的特点,互相不可替代。
int main()
{
int a = 0;
int& b = a;//语法上不开空间
int* p = &a;//语法上要开空间
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int* ptr = NULL;
int& r = *ptr;//底层实现的时候这里实际存的是*ptr的地址,所以还是NULL,并没有对NULL解引用,所以这一步不会报错
cout << r << endl;//在这里会出错,因为这里使用r是要对NULL解引用的
return 0;
}
//频繁调用的小函数
//C -> 宏函数
//#define ADD(a, b) ((a) + (b))
#include <iostream>
using namespace std;
//Debug版本下面默认是不展开的->方便调试
inline int Add(int a, int b)
{
int ret = a + b;
return ret;
}
int main()
{
//可以通过汇编观察程序是否展开
//有call Add语句就是没有展开,没有就是展开了
int c = Add(1, 2);
cout << c << endl;
return 0;
}
//F.h
#include <iostream>
using namespace std;
inline void f(int i);
//F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
//Test.cpp
#include "F.h"
int main()
{
// 链接错误:无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z)
f(10);
return 0;
}
面试题:
宏的优缺点?
优点:
缺点:
C++有哪些技术代替宏?
int main()
{
int j = 0;
//右边初始化自动推导类型
auto i = 0;
return 0;
}
#include <map>
#include <string>
int main()
{
std::map<std::string, std::string> dict;
//以下两行代码是等价的,auto可以替代写起来比较长的类型的定义,简化代码
//std::map<std::string, std::string>::iterator it = dict.begin();
auto it = dict.begin();
return 0;
}
typedef也有类似的功能,但是没有auto方便,而且typedef有一些缺点:
typedef char* pstring;
int main()
{
//const pstring p1;//err 这里的const修饰的是p1,const修饰的是指针本身就必须要初始化指针
const pstring* p2;//这里const修饰的是*p2,p2是可以改变的,所以可以不初始化
return 0;
}
我们认识一下typeid,它可以帮助我们去看一个对象的类型,记一下用法:
#include <iostream>
using namespace std;
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;//int
cout << typeid(c).name() << endl;//char
cout << typeid(d).name() << endl;//int
return 0;
}
还有一些其他用法:
int main()
{
int x = 10;
//以下两行代码是等价的,但是auto*指定了必须是指针,auto就没有要求,传什么都可以
auto a = &x;
auto* a = &x;
//引用
auto& c = x;
return 0;
}
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量:
void TestAuto()
{
auto a = 1, b = 2;
//auto c = 3, d = 4.0;//该行代码会编译失败,因为c和d的初始化表达式类型不同
}
auto不能作为函数的参数:
//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{
}
auto不能直接用来声明数组:
void TestAuto()
{
int a[] = { 1, 2, 3 };
//auto b[] = { 4, 5, 6 };//err
}
之前我们是这样写for循环的:
#include <iostream>
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << " ";
}
cout << endl;
return 0;
}
现在我们可以这样写:
#include <iostream>
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
//array中的值赋值给e,改变e不会影响数组中的值,如果想要改数组中的值就要加上引用
for (auto& e : array)
{
e *= 2;
}
//C++11 范围for
//自动取数组array中的值,赋值给x
//自动++,自动判断结束
for (int x : array)//这里用auto还是具体的类型都可以;变量名也可以随便取
{
cout << x << " ";
}
cout << endl;
return 0;
}
下面这种写法是不对的:
#include <iostream>
using namespace std;
//不支持,因为数组传参之后就变成指针了
void TestFor(int array[])
{
for (auto& e : array)
{
cout << e << endl;
}
}
NULL实际是⼀个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦:
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
// 本想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。
f(NULL);
f((int*)NULL);
f(nullptr);
return 0;
}