赞
踩
C++初识
#include <iostream>
//test.cpp
using namespace std;
int main()
{
std::cout << "hello world" << std::endl;
cout << "haha" << endl;
return 0;
}
使用using将命名空间展开,使用cout标准输出流,和流插入运算符 << 将hello world输出到控制台。使用endl来完成C语言的换行。C++是在C语言之上发展出来的,在C++的编译器里是兼容C语言的,两者可以混合使用。
在C/C++中,变量、函数、类大量存在,由于它们的大量存在而可能导致名称使用上的冲突。namespace关键字,是针对名称冲突而出现的。
#include <iostream>
namespace hx
{
int rand = 6;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
int val;
struct Node* next;
};
}
namespace本质上是定义了一个域,这个域跟全局域各自独立,不同域可以定义同名变量。
想要使用名称空间的成员,必须在变量/函数/结构体/……前加上名称空间的名字和 ::
域作用限定符。否则默认使用,其它地方出现的变量/函数/结构体/……
名称空间只能在全局定义,可以嵌套使用
项目工程里,多文件定义同名namespace会认为是同一个namespace,不会发生冲突
C++标准库都放在一个std的命名空间中。
案例1:
#include <iostream> namespace hx//命名空间的名字 { int range = 6; void swap(int x, int y) { std::cout << "void swap(int x, int y)" << std::endl; } struct Node { int val; struct Node* next; }; } void swap(int& x, int& y) { int temp = x; x = y; y = temp; } int main() { int a = 5; int b = 10; int range = 1; printf("%d %d\n", range, hx::range); swap(a, b); printf("%d %d\n", a, b); hx::swap(a, b); return 0; }
案例2(嵌套定义):
namespace xyz { namespace xy { int range = 6; void swap(int x, int y) { std::cout << "void swap(int x, int y)" << std::endl; } } namespace yz { int range = 3; void swap() { std::cout << "void swap()" << std::endl; } } } int main() { int range = 66; std::cout << xyz::xy::range << std::endl; std::cout << xyz::yz::range << std::endl; }
::
,域作用限定符。
::变量名
,访问全局变量。
命名空间名::变量名
,访问了命名空间域。
struct hx::Node node;
,访问结构体。
#include <iostream> using namespace std; namespace hx { int rand = 0; void Print() { cout << "哈哈哈" << endl; } struct Node { int val; struct Node* next; }; } int rand = 10; int main() { int rand = 10; cout << ::rand << endl; cout << rand << endl; hx::Print(); struct hx::Node node; return 0; }
使用using将名称空间全部成员展开,在项目中不推荐,名称之间的冲突很大。日常做练习,测试方便可以使用,但不能养成创建一个main函数前,第一件事将,std命名空间展开,以及忘记对std命名空间的成员前使用域作用限定符的坏习惯。
使用using展开前,编译器是先查找局部,然后查找全局,有了using将命名空间展开后还会到命名空间内查找,有一种将命名空间成员,改变为全局变量的意味。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
cin >> a;
cout << a << endl;
return 0;
}
使用using展开后,就不需要频繁使用 std::
,方便了许多。
项目中经常使用,且不会存在冲突的名称
#include <iostream>
namespace hx
{
int n = 10;
int num = 20;
}
using hx::num;
int main()
{
cout << num << endl;
return 0;
}
C++中域有函数局部域、全局域、命名空间域、类域,域影响的是变量/函数/类型的出处的逻辑, 有了域隔离(不同的域可以同名,同一个域不可以定义同一个东西),名字冲突的问题就解决了。
局部域和全局域除了会影响编译时的查找逻辑(先局部查找,然后全局查找),还会影响变量的生命周期。
局部变量存储在栈上,生命周期时临时的,结束函数的调用或作用域结束时消亡。
全局变量存储静态存储区上,生命周期是永久的,从程序开始执行诞生,到程序终止时消亡。
命名空间域和类域不影响变量生命周期。
命名空间域,只能定义在全局,它的成员本质上是全局变量。
命名空间域,与全局进行隔离
编译时想要使用变量/函数/结构体/……编译时向上查找过程必须包含对应的代码,先局部查找,然后全局查找。
< iostream >,是Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。
std::cin
,是 istream 类的对象,主要面向窄字符的标准输出,与C语言的不同,它不需要使用占位符,可以自动识别任意类型的变量。使用流提取运算符(>>), cin >> 变量名 << endl
,
std::cout
,是ostream类的对象,主要面向窄字符的标准输出流,不需要使用占位符,%d、%s、%c这些东西,可以自动识别任意类型的变量、数据类型进行输出,使用流插入运算符(<<)。
std::endl
(end line),是一个函数,流插入、输出时,相当于插入一个换行符 加 刷新缓冲区。
‘\n’
,这种换行方法,在不同的系统上可能不兼容,无法通过换行符进行换行,使用endl更加多用。使用C++输入输出更加方便,不需要像printf、scanf输入输出时那样,需要使用占位符手动指定格式,C++的输入输出可以自动的识别变量的类型。
C++兼容C语言,头文件< iostream >
,间接包含了printf、scanf,在vs编译器上是这样,其它编译器不一定能支持。
使用cout进行输出时,输出内容不会第一时间输出,而是先将这些东西装换为字符串,然后放在cout的缓冲区,需要遇见一些刷新标志才会出缓冲区,比如换行、flush(cout的主动刷新接口)才会输出到控制台中。
这三句代码可以提高,C++IO流的效率
int main()
{
ios_base::sync_with_stdio(false);//使C语言和C++不同步兼容
cin.tie(nullptr);
cout.tie(nullptr);//将cin和cout相互绑定的关系,不会与其它流相互关联
return 0;
}
C++中的缺省参数(默认参数),允许在对函数进行声明或定义时为函数参数提供一个缺省值。在调用该函数时,如果没有传递参数,则使用该形参对应的缺省值,否则使用传递过来的参数,缺省参数分为全缺省和半缺省
函数声明和定义分离时,缺省参数不能再函数声明和定义中同时出现,规定必须哈数声明给缺省值,例如,实现数据结构队列的.h头文件的函数声明和.c文件对函数的定义。
#include <iostream>
using namespace std;
int Add(int a = 1, int b = 2, int c = 3)
{
return a + b + c;
}
int main()
{
cout << Add() << endl;
cout << Add(10) << endl;
cout << Add(10, 20) << endl;
cout << Add(10, 20, 30) << endl;
return 0;
}
全缺省参数的,几种调用形式
Add()
,没有传递参数,调用函数add是使用三个缺省值。Add(10)
,传递了第一个参数,没有后续两个参数,形参b和c使用缺省值。Add(10, 20)
,传递了第一个和第二个参数,没有最后一个参数,形参c使用缺省值。Add(10, 20, 30)
,三个参数都传递了,不会使用缺省值。#include <iostream>
using namespace std;
int Add(int a, int b, int c = 3)
{
return a + b + c;
}
int main()
{
cout << Add(10, 20, 30) << endl;
return 0;
}
例如,在对栈的数据结构进行初始化时,可以给一个缺省值,我可以不传递参数让其默认开辟4个空间。
//初始化
void StackInit(Stack* ps, int n = 4)
{
assert(ps);
ps->arr = (StackDatdType*)malloc(sizeof(StackDatdType) * n);
ps->capacity = ps->top = 0;
}
C++中允许在同一个作用域里定义多个同名函数,只要它们的参数列表不同。这种特性使得同一个函数名可以执行不同的操作,C语言不支持这种操作。
#include <iostream> int Add(int left, int right) { return left + right; } double Add(double left, double right) { return left + right; } int main() { Add(1, 2); Add(1.2, 5.2); return 0; }
这里实际上调用了两个函数
这里同时使用了函数重载和缺省参数,语法上没问题,当直接调用 test()
,编译器直接傻眼了不知道到的用那个函数,有歧义。
void test()
{
cout << "void test()" << endl;
}
void test(int n = 4)
{
cout << "void test(int n = 4)" << endl;
}
int main()
{
test();
return 0;
}
很牛的东西。
引用就是取别名,引用不是新定义一个变量,而是给以存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和引用的变量共同使用同一块内存空间。比如土豆、洋芋、马铃薯,不同的称呼,指向的都是同一个东西。
C++中为了避免引⼊太多的运算符,会复⽤C语⾔的⼀些符号,⽐如前⾯的<<和>>,这⾥引⽤也和取 地址使⽤了同⼀个符号&。
类型& 引用别名 = 引用对象
int main()
{
int a = 6;
int& b = a;
int& c = a;
int& d = b;//也可以对别名b取别名,d也是指向变量a
d++;//对d加加,a、b、c都会加1,它们指向同一块内存。
}
int main()
{
int a = 6;
int& b = a;
int& c = a;
int& d = b;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
d++;
cout << a << endl;
}
引用在定义时必须初始化
一个变量可以有多个引用(上面提过例子)
引用一旦引用一个实体,就不能引用其它实体,这里使用ra对变量a引用之后,还使用b进行引用,编译代码时没有报错,但运行后会报错,提示ra多次初始化
int main()
{
int a = 1;
int b = 10;
int& ra = a;
int& ra = b;
//这里并非是是引用,而是一个赋值,将10赋给ra
ra = b;
return 0;
}
引用在实践中主要时用于传参和引用做返回值中,减少拷贝提高效率和改变引用对象时同时改变被用于对象。
例如,我传递100w个大小的整形数组,来解决某问题,传递数组时需要临时开辟100w个大小的整形空间,非常浪费,这里对数组使用引用就可以进行优化,从而提高效率。指针也能达到这种效果。
对函数使用引用本质上是在传地址
引用传参跟指针传参相对比较复杂,指针也也有类似场景。
int& StackTop(Stack& ps) { assert(ps.top > 0); return ps.arr[ps.top - 1]; } int main() { Stack sk; StackInit(&sk); StackPush(&sk, 1); StackPush(&sk, 2); StackTop(sk)++; return 0; }
这里将栈顶元素放回后++,编译器会报错。调用函数返回后,它的返回值存放在一块临时对象中它具有常性无法被修改。
解决办法:将返回值进行引用,这里返回的就不会将返回值拷贝在临时对象中,而是给这个返回值取了一个别名,返回的是栈顶元素的别名,对别名加加的结果是栈顶元素大小加1。
int& StackTop(Stack& ps) { assert(ps.top > 0); return ps.arr[ps.top - 1]; } int main() { Stack sk; StackInit(&sk); StackPush(&sk, 1); StackPush(&sk, 2); StackTop(sk)++; return 0; }
无法使用引用进行返回的场景
//指针 int* test() { int ret = 10; //…… return &ret;//返回的是局部变量,函数调用完成后被销毁,返回地址的是一个野指针。 } //引用 int& test() { int ret = 10; //…… return ret;//引用同理,引用一块被销毁的空间 } int main() { test()++; return 0; }
#include <iostream> using namespace std; void Swap(int& x, int& y) { int temp = x; x = y; y = x; } int main() { int a = 10; int b = 50; Swap(a, b); cout << a << endl << b; return 0; }
可以用于const对象,但是必须使用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但不能放大。指针也存在权限放大缩小。
int main() { const int a = 0; //编译器报错 error C2440: “初始化”: 无法从“const int”转换为“int &” //int& ra = a;//权限放大 const int& ra = a;//平等关系 int b = 0; int& rc = b; const int& rd = b;//权限缩小 //error C3892: “rd”: 不能给常量赋值 //rd++; rc++; return 0; }
类似于对 int& rb = b * 2; float& d = 3.14; int& rd = d;
。进行引用。对 b * 2
的结果会存放在一个临时变量中,将b * 2的结果计算出来后,编译器放在一个临时对象中,存储中间值。int& rd = d;
对一个浮点数进行引用,先对变量d进行类型转换,这个类型转换的结果也会被存放在临时对象中。C++规定临时对象具有常性(常性,形如被const修饰),这里触发了权限放大,必须使用常引用才可行。
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
const修饰的引用作为函数参数,可以引用临时对象、普通对象、const修饰的对象。
void fun(const int& ra);
使用inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开内联函数,这种调用内联函数的方式就不需要要创建函数栈帧,提高了效率。
//#define ADD(a, b) a + b;
//#define ADD(a, b) (a + b)
//#define ADD(a, b) ((a) + (b))
// 为什么不能加分号? 例如if语句调用加分号的ADD函数
// 为什么要加外⾯的括号,为什么要加⾥⾯的括号?
C语言debug版本默认不展开inline。debug版本想要展开需要设置这两点。
找到C/C++选项里的常规,将调试信息格式,设置为 程序数据库(/Zi)
找到C/C++选项里的优化,将内联函数拓展设置为 只适用 _inline(/Ob1)
NULL是一个宏,在C语言头文件里(stdd.f)可以观察。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*) 0)
#endif
#endif
在这种情况下,传递NULL想调用int*版本的f函数,但调用的结果时int版本的函数,为了弥补这样的不足,C++引用了关键字nullptr
void f(int x)
{
cout << "void f(int x)" << endl;
}
void f(int* x)
{
cout << "void f(int* x)" << endl;
}
int main()
{
f(1);
f(NULL);
return 0;
}
nullptr在C++11中引用的特殊关键字,nullptr是一种特殊类型的字面常量,它可以转换为其它类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。