在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全 局作⽤域中,可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名冲突或名字污染 。
在同一个工程中,不同文件中定义的同名namespace会被认为是同一个namespace,不会冲突。
命名空间内可以嵌套命名空间,如示例代码:
namespace Kevin
{
namespace star
{
string star = "Messi";
}
namespace sport
{
string sport = "Football";
}
}
在使用时进行展开使用:
cout << Kevin::sport::sport << Kevin::star::star << endl;
• 指定命名空间访问,项⽬中推荐这种⽅式。 • using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。 • 展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。
• 是InputOutputStream的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输 出对象。 • std::cin是istream类的对象,它主要⾯向窄字符(narrowcharacters(oftypechar))的标准输⼊流。 • std::cout是ostream类的对象,它主要⾯向窄字符的标准输出流。 • std::endl是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。 • <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移) • 使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输⼊ 输出可以⾃动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出
缺省参数分为:全缺省,半缺省参数(默认参数)。
请注意:
全缺省就是参数部分全部给缺省值
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
可以如下使用:
Func1();
Func1(1,2,3);
半缺省就是部分形参给缺省值
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
可以如下使用:
Func2(1);
Func2(1,2);
Func2(1,2,3);
C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者 类型不同。
样例如下:
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
样例如下:
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
样例如下:
// 3、参数类型顺序不同
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;
}
当返回值不同的时候,我们在使用的时候无法识别出要使用哪一个函数,因为使用哦的时候仅仅是调用,不会显式的告诉编译器你要用哪个返回值的函数,所以返回值不同的函数不能构成重载。
// 返回值不同不能作为重载条件,因为调⽤时也⽆法区分
void fxx()
{}
int fxx()
{
return 0;
}
区别于构造函数,类外的全缺省参数函数与无参函数构成的函数重载无法正常调用,存在歧义。当调用时编译器不知道应该使用哪一个函数。
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
给变量取别名,编译器不会为引用变量开辟内存空间,它和引用的变量共用一块内存空间。也就是说,通过引用可以直接改变引用的变量。(无法直接代替指针)
使用格式:
类型& 引用别名 = 引用对象;
• 引⽤在定义时必须初始化 • ⼀个变量可以有多个引⽤ • 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
引用需要再定义的时候就进行初始化,如果执行以下代码会报错:
int& ra;
ra = a;
正确格式:
int& ra = a;
int& ra = a;
int& aa = a;
int& ba = a;
可以根据上述代码进行多个引用别名引用同一个变量,通过修改ra,aa,ba都可以改变a的值。
int& ra = a;
int& ra = b; //error
当已经在定义的时候引用一个实体变量后,在后续这个别名就不能再引用别的变量了。
在链表中,节点就是指针,无法用指针替代。
我们使用链表的尾插进行讲解,代码如下:
#include <stdlib.h>
typedef struct LTNode {
int val;
struct LTNode* next;
} LTNode;
typedef struct LTNode* PNode;
void ListPushBack(PNode** phead, int x) {
// 动态分配内存给新节点
PNode newnode = (PNode)malloc(sizeof(LTNode));
if (newnode == NULL) {
// 内存分配失败的处理
return;
}
newnode->val = x; // 设置新节点的值
newnode->next = NULL; // 新节点的next指针初始化为NULL
// 如果链表为空,新节点即为头节点
if (*phead == NULL) {
*phead = newnode; // 将头指针指向新节点
} else {
// 如果链表不为空,找到链表的最后一个节点
PNode current = *phead;
while (current->next != NULL) {
current = current->next;
}
// 将最后一个节点的next指针指向新节点
current->next = newnode;
}
}
为什么要用二级指针?
当用C++风格修改后的函数:
void ListPushBack(PNode*& pheadRef, int x) {
// 动态分配内存给新节点
PNode newnode = (PNode)malloc(sizeof(LTNode));
if (newnode == NULL) {
// 内存分配失败的处理
return;
}
newnode->val = x;
newnode->next = NULL;
// 如果链表为空,新节点即为头节点
if (pheadRef == NULL) {
pheadRef = newnode; // 使用引用直接修改头指针
} else {
// 如果链表不为空,找到链表的最后一个节点
PNode current = pheadRef;
while (current->next != NULL) {
current = current->next;
}
// 将最后一个节点的next指针指向新节点
current->next = newnode;
}
}
当使用PNode*&
的时候与int&
类似,表示一个结构体指针的引用。在函数中直接通过修改phead的值就可以修改头结点的指向,而不需要像二级指针一样需要解引用才可以改变一级指针指向 。
例如想要改变栈顶元素:
int STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top - 1];
}
int main()
{
STTop(st1) = 3;
}
该代码运行会报错:
必须为左值元素(可修改,赋值变量)
当该返回值为int
时,返回的临时变量STTop(st1)
为常量,常量无法修改。
用引用来解决该问题。
当用引用做返回值,就是返回rs.a[rs.top - 1]
的引用。此时当再用STTop(st1) = 3;
修改栈顶数据即可完成。
代码书写可能会有以下错误:
const int a = 10;
int& ra = a; // error
当使用
int&
来接收const int
类型的值时,对于int&
来说相当于把权限放大了,而权限放大时不允许的。因为原来的a
是一个不可修改的int
常量,而如果要用int&
接收的话,int&
本身是要对所引用的对象进行修改等操作的,如此即违规了。
但是如下代码相反:
int a = 10;
const int& ra = a; // yes
原因如上述的相反结论,即缩小了权限,权限的缩小是被允许的。
(表达式1 + 表达式2)在赋值时也会产生临时对象
如下:
const int a = 10;
int b = 20;
int& rab = (a + b); // error
与上述类似:int&
如果接受一个常量会将权限放大,不允许。
const int a = 10;
int b = 20;
int rab = (a + b); // yes
但当使用一个整型变量接收的话会直接对临时对象进行拷贝,拷贝不涉及权限的放大缩小,仅仅是数据上的拷贝!
int&
是为了取别名对引用对象进行操作,而拷贝只是进行数据的拷贝。
• 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。 • 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。 • 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。 • sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte) • 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些
inline对于编译器⽽⾔只是⼀个建议 ,用inline修饰的函数也叫内联函数,编译的时候C++编译器会在调用的地方将函数内的代码全部展开,这样调用内联函数就不用寄哪里栈帧了,从而提高效率。
格式一般如下:
inline int Add(int x, int y)
{
int ret = x + y;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
注意:
#define ADD(a, b) ((a) + (b))
// 为什么不能加分号?
// 为什么要加外⾯的括号?
// 为什么要加⾥⾯的括号?
cout << 3 * ADD( 1 , 2 ) << endl;
时就会因为运算符优先级的问题而计算错误。所以在定义宏函数的时候将括号写准确有利于后续程序的稳定。void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。 由于在C++中NULL被定义成了0,所以例如:调⽤了
f(int x)
,因此与程序的初衷相悖。f((void*)NULL);
调⽤会报错。