初识C++
1、命名空间
1.1、命名空间的概念
命名空间的出现主要是为了解决函数和类的名称出现在全局作用域的时候导致的冲突问题,这个时候可以使用C++关键字namespace对标识符的名称进行本地化,以避免命名冲突或者名字污染。
1.2、命名空间的使用方法
1、加命名空间名称及作用域限定符
2、使用using将命名空间中成员引入
3、使用using namespace 命名空间名称引入
#include <iostream>
//1、普通命名空间
namespace N1 //N1为命名空间的名称
{
//命名空间中的内容,既可以定义变量,也可以定义函数
int a;
int Add(int left, int right)
{
return left + right;
}
}
//2、命名空间的嵌套使用
namespace N2
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N3
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
//3、同一个工程允许出现多个相同名称的命名空间(编译器最后会合并到同一个命名空间中)
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
//一个命名空间就定义了一个新的作用域,命名空间中所有的内容都会局限于该命名空间中
//命名空间的三种使用方式
namespace N
{
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
int Mul(int left, int right)
{
return left * right;
}
}
//1、加命名空间名称及作用域限定符
int main()
{
printf("%d\n", N::a);
system("pause");
return 0;
}
//2、使用using将命名空间中成员引入
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
system("pause");
return 0;
}
//3、使用using namespace 命名空间名称引入
using namespace N;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
system("pause");
return 0;
}
1.3、命名空间的优点
1.名字空间可以解决函数,全局变量名重复的问题,包括在不同名字空间里的重复函数,实际就是两个完全无关的函数;
2.名字空间允许嵌套,嵌套内部和外部没有任何关系;
3.名字空间允许重名,编译器最后会合成同一个命名空间中.
2、缺省参数
2.1、缺省参数概念
缺省参数是指在函数声明或者定义的时候,给函数参数传一个默认值,在调用函数的时候,若是没有给指定参数传参,则使用默认值,否则使用传入的参数
2.2、缺省参数的分类
1、全缺省参数
2、半缺省参数
#include <iostream>
using namespace std;
void testfun(int a = 10)
{
cout << a << endl;
}
void testfun2(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
void testfun3(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main3()
{
testfun();
testfun(20);
testfun2();
testfun2(40);
testfun2(40, 50);
testfun2(40, 50, 60);
//testfun3();错误
testfun3(40);
testfun3(40, 50);
testfun3(40, 50, 60);
system("pause");
return 0;
}
注:
1、半缺省参数的参数必须从右向左一次给出,不能有间隔
2、缺省参数不能在声明和定义中同时出现
3、缺省参数必须是全局变量或者常量
4、也属于函数重载的一种类型
3、函数重载
函数重载是指在C++允许在同一作用域中声明几个功能类似的同名函数,这些函数的参数列表必须不同。
#include <iostream>
using namespace std;
int add(int left, int right)
{
return left + right;
}
double add(double left, double right)
{
return left + right;
}
long add(long left, long right)
{
return left + right;
}
int main4()
{
add(10, 20);
add(10.0, 20.0);
add(10l, 20l);
system("pause");
return 0;
}
4、名字修饰
名字修饰是指C++在编译过程中,将函数、变量名称重新改编的机制
C语言的名字修饰是在函数名字前面加了下划线,容易产生冲突
c++中,名字修饰是由"?函数名@域名1@域名2…@@参数列表@Z"构成的,包含
a.函数名
b.域名
c.参数列表
所以在c++中,以上三个必须完全相同,才会出现冲突,这就是函数重载原理.
5、extern “C”
在C++中有时候需要用到C的编译风格来编译代码,就需要在函数前加extern “C”
#include <iostream>
using namespace std;
extern "C"
{
int Add(int left, int right)
{
return left + right;
}
}
int main()
{
cout << Add(10, 20) << endl;
system("pause");
return 0;
}
6、引用
6.1、引用概念
引用是指给变量起别名,编译器不会为引用开辟新的内存空间,它和它引用的变量共用一块内存空间
引用的类型必须和实体的类型一样
6.2、引用特性
1、引用在定义时必须初始化
2、一个变量可以有多个引用
3、引用一旦引用一个实体不能再引用其他实体
6.3、引用的使用场景
1、常引用
2、做参数
3、做返回值
#include <iostream>
using namespace std;
//引用不是新定义一个变量,而是给已经存在的变量起别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
//eg:类型& 引用变量名 = 引用实体
void Test6()
{
int a = 10;
int& aa = a;
printf("%p\n", &a);
printf("%p\n", &aa);
}
//引用特性
//1、引用用在定义时必须初始化
//2、一个变量可以有多个引用
//3、引用一旦引用一个实体,再不能引用其他实体
void TestRef()
{
int b = 20;
//int& ra;
int& ra = b;
int& rra = b;
printf("%p %p %p", &b, &ra, &rra);
}
//常引用
void TestConstRef()
{
const int a = 10;
//int& ra = a; a为常量
const int& ra = a;
//int& b = 10; b为常量
const int& b = 10;
double d = 3.14;
//int&rd = d; 类型不同
const int& rd = d;
}
//使用场景
//1、做参数
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
//2、做返回值
int& TestRefReturn(int& a)
{
a += 10;
return a;
}
int& Add2(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
Test6();
TestRef();
int& ret = Add2(10, 20);
Add2(3, 4);
cout << "Add2(10, 20) = " << ret << endl;
system("pause");
return 0;
}
6.4、传值和传引用的区别
传值或者返回值,函数不会直接传递实参或者将变量本身直接返回,而是传递或者返回值的一份临时拷贝,效率较低
==作为参数传值或者传引用的比较==
#include <iostream>
#include <ctime>
using namespace std;
struct A
{
int a[10000];
};
void TestFun1(A a)
{
}
void TestFun2(A& a)
{
}
void TestRefAndValue()
{
A a;
size_t begin1 = clock();
for (int i = 0; i < 10000; i++)
{
TestFun1(a);
}
size_t end1 = clock();
size_t begin2 = clock();
for (int i = 0; i < 10000; i++)
{
TestFun2(a);
}
size_t end2 = clock();
cout << "TestFun1() Time = " << end1 - begin1 << endl;
cout << "TestFun2() Time = " << end2 - begin2 << endl;
}
int main()
{
for (int i = 0; i < 100; i++)
{
TestRefAndValue();
}
system("pause");
return 0;
}
==作为返回值传值和传引用的比较==
#include <iostream>
#include <ctime>
using namespace std;
struct A
{
int a[10000];
};
A a;
A TestFun1()
{
return a;
}
A& TestFun2()
{
return a;
}
void TestRefAndValue()
{
size_t begin1 = clock();
for (int i = 0; i < 10000; i++)
{
TestFun1();
}
size_t end1 = clock();
size_t begin2 = clock();
for (int i = 0; i < 10000; i++)
{
TestFun2();
}
size_t end2 = clock();
cout << "TestFun1() Time = " << end1 - begin1 << endl;
cout << "TestFun2() Time = " << end2 - begin2 << endl;
}
int main()
{
for (int i = 0; i < 100; i++)
{
TestRefAndValue();
}
system("pause");
return 0;
}
6.5、引用和指针的区别
1、引用必须初始化,指针没要求;
2、引用在初始化引用一个实体后不能再引用其他实体,指针可以指向同一个类型的实体;
3、没有NULL引用,有NULl指针;
4、在sizeof中含义不同:引用是引用类型的大小,指针是地址空间所占字节个数;
5、引用自加就是实体+1,指针自加加的是偏移量;
6、引用没有多级,指针有多级指针;
7、引用编译器自己处理,指针需要显示解引用;
8、引用比指针安全.
6.6、注意
如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引用类型返回,如果以引用类型返回,返回值的生命周期将会变长且不受限制
#include <iostream>
using namespace std;
int& Add2(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
Test6();
TestRef();
int& ret = Add2(10, 20);
Add2(3, 4);
cout << "Add2(10, 20) = " << ret << endl;
system("pause");
return 0;
}
7、内联函数
7.1、内联函数概念
在C++中存在inline关键字,这个关键字修饰的函数称为内联函数,编译C++时,会调用内联函数的地方展开,没有压栈的开销,是一种空间换时间的做法。
7.2、内联函数特性
1、是空间换时间的做法,用减少压栈的开销来提升效率,但是循环和递归一般不适宜作为内联函数
2、inline对编译器来说是建议类关键字,编译器会自动优化,遇到函数或者递归会忽略优化
3、inline不建议声明和定义分离,分离会导致链接错误。
#include <iostream>
using namespace std;
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int res = 0;
res = Add(10, 20);
cout << res << endl;
system("pause");
return 0;
}
7.3、宏和内敛函数的对比
1、宏的优点:
1.1、增强代码的复用性
1.2、提高性能
2、宏的缺点:
2.1、不方便调试
2.2、代码可读性和可维护性差
2.3、没有类型安全检查
C++可以采用常量定义换成const 函数定义 加inline关键字等技术代替宏
8、auto关键字
8.1、auto的概念
auto是在C阶段是一个存储类型指示符
auto在C++11中,auto是新的类型指示符,auto声明的变量必须由编译器推导得到。
注:
auto定义时必须初始化,auto代表类型声明的“占位符”
8.2、auto使用规则
1、auto与指针和引用结合使用
用auto声明指针时,用auto和auto*没有区别,但是引用必须是auto&
2、同一行定义多个变量,则这一行的类型必须相同,否则编译器报错,编译器只对第一个类型进行推导
8.3、auto不能推导的场景
1、auto不能作为函数参数
2、auto不能直接用来声明数组
3、auto在c++11只能作为类型指示符
4、auto实际常见用法就是范围for和lambda表达式配合使用
5、auto不能定义类的非静态成员变量
6、实例化模板不能使用auto作为模板参数
#include <iostream>
#include <typeinfo>
using namespace std;
//auto作为一个新的类型指示符来指示编译器,auto声明的变量必须有编译器在编译时推到得到
//注:
//使用auto时必须对变量进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。
//auto是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
double testauto()
{
return 10.0;
}
//auto的使用
//1、auto与指针和引用结合使用
//用auto声明指针类型时,用auto和auto*没有区别,在auto在声明引用的时候必须加&
void testauto1()
{
int a = 10;
auto b = &a;
auto* c = &a;
auto& d = a;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
}
//2、在同一行定义多个变量
//当在同一行声明多个变量,这些变量必须是相同的类型,否则编译器将会报错,因为编译器支队第一个变量进行推导。
void testauto2()
{
auto a = 1, b = 2;
//auto c = 1.2, d = 4;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = testauto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
system("pause");
return 0;
}
9、范围for
9.1、范围for概念
对于一个有范围的集合,C++11引入了基于范围for循环,for循环由两部分组成,前半部分是范围内迭代的变量,:,后半部分是被迭代的范围
9.2、范围for的使用条件
1、循环的边界确定
对于数组的边界就是第一个元素和最后一个元素的范围,对于类而言,就是类提供的begin方法和end方法。
2、迭代的对象要实现++和==的操作
#include <iostream>
using namespace std;
//C++98遍历一个数组
void TestFor()
{
int _array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (int i = 0; i < sizeof(_array) / sizeof(_array[0]); i++)
{
_array[i] *= 2;
}
for (int* p = _array; p < _array + sizeof(_array) / sizeof(_array[0]); p++)
{
cout << *p << " ";
}
cout << endl;
}
//C++11用范围for遍历
//格式:for(范围内用于迭代的变量:表示被迭代的范围)
void TestFor11()
{
int _array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (auto& e : _array)
{
cout << e * 2 << " ";
}
cout << endl;
}
//范围for的使用条件
//1、for循环的范围必须确定(对于数组而言,就是数组第一个和最后一个元素的范围,对于类而言,类的begin方法和end方法是迭代的范围)
//2、迭代的对象要实现++和==
int main()
{
TestFor();
TestFor11();
system("pause");
return 0;
}
10、指针空值nullptr(C++11)
10.1、nullptr介绍
出现nullptr的原因 NULL是一个宏,可能被定义为字面常量0,或者被定义为无类型指针(void)*)的常量,会造成二义。
C++11中,没有消除二义性,但给出了全新的nullptr表示空值指针。nullptr代表一个指针空值常量,nullptr是有类型的,其类型为nullptr_t仅仅被隐式转化为指针类型。
10.2、注意:
1、nullptr是关键字,不需要包含头文件
2、sizeof(nullptr)和sizeof((void*)0)所占字节相同
3、为了提高代码的健壮性,使用空值指针时候,最好使用nullptr
#include <iostream>
using namespace std;
typedef decltype(nullptr) nullptr_t;
//注:
//1、在使用nullptr表示空值指针时,不需要包含头文件,nullptr是关键字
//2、在C++11中sizeof(nullptr)和sizeof((void*)0)所占的字节数相同
//3、为了提高代码的健壮性,使用空值指针时候,最好使用nullptr
void Fun(int)
{
cout << "Fun(int)" << endl;
}
void Fun(int*)
{
cout << "Fun(int*)" << endl;
}
int main()
{
Fun(0);
Fun(NULL);
Fun((int*)NULL);
system("pause");
return 0;
}
11、C++关键字
以后会详解。