c++(入门)

命名空间

使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

namespace wjj
{
	int rand = 20;
    struct Node
	{
		int data;
		struct Node* next;
	}
	namespace blb
	{
		int c = 10;
	}
}
int main()
{
	printf("%d\n", wjj::rand);
	printf("%d\n", wjj::blb::c);
    struct wjj::Node node1;//错误使用:wjj::struct Node node2;
	return 0;
}
//命名空间的展开
using namespace std;//全部展开,将辛苦建立起来的c++命令空间破坏,一般情况下不建议这么使用。但是此时命名空间仍然存在,也就是我们也可以使用std::cout。
using std::cout;//部分展开,也就是单独将cout放到全局域当中,编译过程现在局部域去找,然后再去全局域当中找
//如果没有上述的操作默认是不会去命名空间里面寻找的



注意事项:

1、命名空间可以嵌套

2、同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。比如一个工程中的test.h和上面test.cpp(test.cpp文件中包含了test.h头文件)中两个wjj会被合并成一个。

输入和输出

cout标准输出对象(控制台)、cin标准输入对象(键盘)、>>是流提取运算符、<<是流插入运算符

endl是特殊的C++符号,表示换行输出,相当于"\n"。

相比printf和scanf的区别是:不需要去手动控制格式,c++的输入输出可以自动识别类别,其二cout和cin通过运算符重载可以更好的适配类和对象

 缺省参数

//传统的初始化方法是没有defaultCapacity形参,默认开空间的大小是4个变量,当我们知道我们需要的空间大小是100个变量的时候,要不断地扩容,但是扩容也是存在一定的消耗的,特别还存在异地扩容的时候。采用缺省值defaultCapacity就一下变得非常的灵活
void StackInit(Stack* ps, int defaultCapacity = 4)
{
	ps->a = (int*)malloc(sizeof(int) * defaultCapacity);
	ps->top = 0;
	ps->capacity = defalutCapacity;
}


void func(int a, int b = 10, int c = 20); 
void func(int a = 10, int b, int c = 20);//半缺省参数必须从右往左依次来给出,不能间隔着给


 //wjj.h
  void Func(int a = 10);
  
 //wjj.cpp
  void Func(int a = 20)
 {}
//如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。正确的写法是缺省值在声明中给,因为main.cpp文件include的是wjj.h文件,并非include的是该函数的具体实现wjj.cpp文件,所以在头文件中给缺省值。

缺省值必须是常量或者全局变量

函数重载

参数类型不同

参数个数不同

参数的类型顺序不同,比如void fun(int a, char b)和void fun(char b, int a)

返回值不同无法构成函数重载,因为调用的时候无法辨别

c++支持函数重载的原理:C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

引用

引用并不是重新定义了一个变量

引用在定义的时候必须初始化,"int& a"这种写法是错误的

引用一旦引用另一个实体,就不能再引用其他实体

int main()
{
	int a = 0;
	int& b = a;
	int& c = b;
	int x = 10;
	c = x;//这里c并不是x的别名,只是把x的值赋值给了c,同时也给了a、b
	return 0;
}
//由于该特性,c++某些地方必须要用指针。比如链表结构当中 
struct Node
{
	struct Node* next;//这里必须用指针,不可以用引用,因为采用引用的话,当我们想要删除后一个节点的时候,就必须修改该节点的next变量,此时由于它是引用的关系,会出现问题
//n1->next=&n2,也就是节点n1的next是节点&n2的引用,删除n2节点之后,无法将n1的next修改成节点&n3的引用
	int val;
}

函数的新参若采用的是引用,那么在调用函数的时候,传参就导致此时形参是实参的别名,新参的改变会影响到实参,不想c语言向改变实参得像函数传递实参的指针做为形参

//常引用,指针和引用,赋值/初始化,权限可以缩小,但是不可以放大。
void TestConstRef()
{
    const int a = 10;
    //int& ra = a;   // 该语句编译时会出错,a被const修饰所以只读,而ra是可读可写的,错将a传给ra会导致权限放大
    const int* p1 = NULL;
    int* p2 = p1;//const修饰*P1也就是p1所指向的空间的值不可修改,但是若在这里将P1传给int*类型的p2,由于p2的类型是可以修改其指向的区间的值,权限放大会报错。
    const int& ra = a;
    // int& b = 10; // 该语句编译时会出错,b为常量
    const int& b = 10;
    double d = 12.34;
    //int& rd = d; // 该语句编译时会出错,类型不同
    const int& rd = d;
    int x = 1;
    const int& y = x;//权限缩小是没有问题的
    int* p3 = NULL;
    const int* p4 = p3;//权限缩小 
}
typedef struct Node
{
	struct Node* next;
	int val;
}Node, * PNode;//两次typedef,后一次相当于typedef struct Node* PNode
void PushBack(PNode& phead, int x)//c语言在该形参PNode& phead处会采用一个二级指针,目的是为了方便修改一级指针phead,但是在c++当中可以采用一级指针的引用来解决这个问题
{
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (phead == nullptr)
	{
		phead = newNode;
	}
    //······
}
//出了栈帧如果变量还是存在的话,就可以采用引用返回
int count1()
{
	int n = 0;
	n++;
	return n;
}
int count2()
{
	static int n = 0;
	n++;
	return n;
}
int& count3()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = count1();//并不是把n给ret,应为当你想把一个变量赋值给ret的时候,count2函数已经走完了。这块空间已经销毁了,空间里面的n已经不在了,所以n在这里无法做
	//实际上在这中间会产生一个临时变量,把n拷贝给临时变量,再把临时变量做为函数的返回值给ret。
	int ret = count2();//此处虽然除了count2栈帧n并没有销毁,但并不会将静态变量n做为返回值给ret,因为编译器没有这么灵活,不会管n是否被销毁,还是像上一次一样,
	//把n拷贝给临时变量,再把临时变量做为函数的返回值给ret。
	int ret = count3();//不会产生临时拷贝,返回的是n的引用g
	return 0;
}



int i = 0;
cout<<(double)i<<endl;
double dd = i;//i先传给double类型的临时变量,然后临时变量再赋值给dd
const double& rd = i;//此时必须要const修饰才行,因为此处rd是doubel类型临时变量的别名,而不是i的别名

引用返回:1、减少拷贝 2、调用者可以修改返回对象

#define N 10
typedef struct Array
{
	int a[N];
	int size;
}AY;
int& PosAt(AY& ay, int i)
{
	assert(i < N);

	return ay.a[i];
}
int main()
{
	AY ay;
	for (int i = 0; i < N; ++i)
	{
		PosAt(ay, i) = i * 10;//此时若将PosAt函数的返回类型给位int,则无法在这个地方对其进行修改。
		//因为此时返回的是ay.a[i]的拷贝,是一个临时变量,它是没有资格称为左值的,也就是无法进行修改。(临时变量具有常性,无法修改)
}

没有NULL引用,但是右NULL指针

在sizeof中引用和指针的含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占的字节数

引用自加引用的实体增加1,指针自加即指针向后偏移一个类型的大小 

内联函数

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。也就是编译期间编译器会用函数体替换函数的调用(call)

 inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。可以将函数规模较小、不是递归、且频繁调用的函数采用inline修饰。

inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。凡是出现链接错误,都是在符号表中找不到函数。而内联函数是不进符号表的,也就是不用将产生的地址放入符号表中,因为内联函数直接展开,不用去calll地址。而main函数编译的时只include了头文件,只有函数声明,链接阶段找函数的obj文件又找不到。所以内联函数直接在头文件中定义即可。

auto

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编 译期会将auto替换为变量实际的类型。

auto是不能做为函数的参数的,比如void func(auto a)。

auto不能直接用来声明数组,比如 auto a[ ] = {1,2,3};

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须 加&

//typedef的缺点
typedef char* pstring;
int main()
{
	const pstring p1;//编译失败,这里编译器不会将p1理解成为const char* p1,而是理解成char* const p1,p1是const变量,必须在定义的时候初始化
}

基于范围的for循环

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围用于迭代的变量,第二部分则表示被迭代的范围。

int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)//如果写成auto e : array,因为这里是自动依次取数组中的数据赋值给e对象,所以e是数组里面指的拷贝,e *= 2并不会改变e,要添加引用才可以。
		e *= 2;
	for (auto e : array)
		cout << e << " ";
	return 0;
}

void TestFor(int array[])//在c++中该行代码就有问题,因为for的范围不确定。
{
    for(auto& e : array)
        cout<< e <<endl;
}
//c语言为了保持效率是不允许传数组的,所以此处c语言会看成int类型的指针,指针不能用这种for循环

nullptr

void f(int)
{
	cout << "f(int)" << endl;
} 
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	/*int* p1 = NULL;
	int* p2 = nullptr;*/
	f(0); //匹配的是f(int)
	f(NULL);//匹配的是f(int),因为c++将NULL定义为了一个宏,#define NULL 0
	f(nullptr);//匹配的是f(int*),可以将nullptr理解成字面量0,被强转成了void*,也就是(void*)0。nullptr是C11的关键字,关键字是语法原生支持的,不需要包头文件
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值