戳这里:我的印象笔记原链接
1.解决C语言中设计不好或者使用不是很方便的语法—>优化
2.增加新的语法特性
注:extern “C”:在C++工程中,将代码按照C语言的风格来编译
用于解决名字冲突,相当于一个作用域
namespace N1
{
1.变量
2.函数
3.命名空间(嵌套)
}
命名空间的定义方式:
1.普通命名空间(只包含变量和函数)
2.命名空间可以嵌套
3.可以创建多个相同名字的命名空间–>合并
命名空间的访问方式:
1.在成员前+ N:: (N为命名空间的名字,::为作用域限定符—默认访问全局的)
2.使用using来指定访问变量:using N2::N3::a;
3.使用using来指定访问命名空间:using namespace N2;
使用标准输入cin和标准输出cout时,必须包含**头文件以及std标准命名空间**
//为了和c区分,c++98之后头文件不需要包含.h
// using namespace std;标准命名空间
cin标准输入(键盘):int a = 0;
double b = 12.34;
cin>>a>>b;
cout标准输出(控制台):cout<<10<<" "<<12.34<
(dec:十进制
oct:八进制
hex:十六进制
二进制可以使用bitset<> 把要输出的数变成二进制存储输出)
概念:声明或定义函数时为函数的参数指定一个默认值(调用时如果没有指定实参就会使用默认值)
全缺省参数:所有参数都有默认值
对于全缺省参数,如果调用函数时只传递了一部分实参,则实参从左往右传递,其余采用默认值
半缺省参数:部分参数带有缺省值,必须从右向左依次给出
对于半缺省参数,要注意对没有给出缺省值的参数传递实参,实参同样从左往右传递
注意:
1.半缺省参数必须从右往左依次给出,不能间隔着给
2.缺省参数不能在函数声明和定义中同时出现(为了避免出现声明和定义不一致情况),最好在声明的位置
3.缺省值必须是常量或者全局变量
4.C语言不支持(编译器不支持)
概念:是函数的一种特视情况,C++允许在同一作用域中声明几个功能类似的同名函数,但这些同名函数的形参列表(参数个数、类型、顺序)必须不同,常用来处理功能类似数据类型不同的问题 //与返回值类型无关,如果只是返回值类型不同,则不能构成重载
二义性:无参函数和同名的全缺省函数不能同时存在
C语言中不支持函数重载是因为:
C语言中编译器对函数名字的修饰规则:只是简单地在函数名字前添加下划线
C++中支持函数重载是因为:
//在vs中通过只给声明不给定义的方式调用函数,编译成功,链接时报错就可以看到编译器对函数名字的修饰规则↓↓↓
C++中编译器对函数名字的修饰规则(_cdecl:C语言缺省调用约定):
int ADD(int left,int right); —> ?ADD@@YAHHH@Z ?函数名@@YA参数表@Z
参数表(返回值和形参类型)符号表示:
void - X
int - H
unsigned int - I
float - M
double - N
bool - N
char - D
short - F
long - J
unsigned long - K
概念:给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
类型& 引用变量名(对象名)= 引用实体
引用特性:
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用了一个实体,就不能再引用其他实体
注意:
1.引用类型必须与引用实体是同类型的
2.引用常量实体时要加const修饰
3.一般情况下,因为引用与实体共用同一块内存空间,所以改变引用的值也就是改变了实体的值
4.引用类型与引用实体不同时加const可以通过编译,此时编译器会为引用创建一个临时变量,这个临时变量具有常属性
使用场景:
1.作形参
如果不需要通过形参修改实参的值,最好的方法是在形参引用前加上const修饰
2.作返回值
如果用引用作为函数的返回值类型,不能返回函数栈上的空间
如果一定要用引用作为返回值,返回的变量生命周期一定要比函数的生命周期长
比如可以这样稍作修改:
传值、传地址、传引用效率比较:
#include
struct A
{
int array[10000];
};
void TestFunc(A& a)
{}
void TestRefPtr()
{
A a;
size_t start = GetTickCount();
for (size_t i = 0; i < 1000000; i++)
TestFunc(a);
size_t end = GetTickCount();
cout << end - start << endl;
}
int main()
{
TestRefPtr();
system("pause");
return 0;
}
通过上面代码的比较,我们发现引用和指针在传参上的效率几乎相同
引用与指针的区别:
不同点:
1.引用在定义时必须初始化,指针没有要求(但最好有一个合法的指向)
2.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任意一个同类型实体
3.没有NULL引用,但有NULL指针
4.在sizeof中含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占的字节个数(32位平台为4个字节)
5.引用自加是引用的实体加1,指针自加是指针向后偏移一个类型的大小
6.有多级指针,但没有多级引用(拓展:C++11中将const int&& rra = 10;这种形式称为右值引用,将普通引用称为左值引用)
7.访问实体的方式不同,指针需要显示解引用,而引用由编译器自己处理
总结来说也可以得出;
1.引用更安全。因为指针在使用之前必须要判空,而引用不需要(因为规定了引用在定义时必须初始化)
2.引用更简洁。引用在使用时代码比较清晰,也不需要解引用操作,写起来简单,看起来舒服,还可以达到指针的效果
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
概念:以inline修饰的函数。编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,提升了程序运行的效率
(ps1:内联函数与宏的替换时机不同,宏替换是在预处理阶段,因此不会对参数类型进行检测)
(ps2:查看方式:通过测试发现,在vs编译器Debug模式下为了便于调试,并没有将inline修饰的函数当做内联函数进行展开,1.可以在Release模式下,查看编译器生成的汇编代码中是否存在call Add,在Release模式下,会对代码进行很大的优化,甚至一些没有实际意义的代码都会直接删除,所以它占用空间会很小,但有可能会打乱程序的执行次序。2.也可以在Debug模式下对编译器进行设置,以下给出vs2010设置方式)
特性
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器(“占位符”),auto声明的变量必须由编译器在编译时期推导而得。
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
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
auto的使用细则
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
auto不能推导的场景
//5. auto不能定义类的非静态成员变量
//6. 实例化模板时不能使用auto作为模板参数
auto的优势
1.在拥有初始化表达式的复杂类型变量声明时的简化
2.可以免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误(程序员不用自己去抉择,编译器根据运算的结果推导)
范围for的语法
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环:for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
(ps:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。)
范围for的使用条件
1. for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
2. 迭代的对象要实现++和==的操作。
1.C++98中的指针空值
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的
错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void)的常量*。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行*强转(void )0。
2.nullptr 与 nullptr_t
为了考虑兼容性,C++11并没有消除常量0的二义性,为了避免混淆,C++11给出了全新的nullptr表示空值指针。即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:
typedef decltype(nullptr) nullptr_t;
注意:
最后附上我的学习代码,仅供参考
#include
using namespace std;
#if 0
//命名空间
#include
#include
//普通命名空间
namespace N1
{
int a = 10;
int b = 20;
int Add(int left,int right)
{
return left + right;
}
}
//命名空间可以嵌套
namespace N2
{
int c = 30;
int d = 40;
int Sub(int left, int right)
{
return left - right;
}
namespace N3
{
int a = 50;
int b = 60;
int Mul(int left, int right)
{
return left * right;
}
}
}
//可以创建多个相同名字的命名空间
namespace N1
{
int Div(int left, int right)
{
return left / right;
}
}
//using N2::N3::a;
using namespace N2;
int main()
{
/*printf("%d\n", ::a);
printf("%d\n", N1::a);
printf("%d\n", N2::N3::a);*/
//printf("%d\n", a);
printf("%d\n", Sub(d, c));
system("pause");
return 0;
}
#endif
#if 0
//标准输入/输出
int main()
{
int a = 0;
double b = 12.34;
cin >> a >> b;
cout << a <<" "<< b << endl;
//cout << hex << a<
cout << 10 << " " << 12.34 << endl;
cout << "hello world!" << endl;
system("pause");
return 0;
}
#endif
#if 0
//缺省参数
int g_a = 9;
void TestFunc(int a = g_a)
{
cout << a << endl;
}
//全缺省参数:所有参数都有默认值
void TestFunc1(int a = 0, int b = 1,int c=2)
{
cout << a << " " << b << " " << c << endl;
}
//半缺省参数:部分参数带有缺省值,必须从右向左依次给出
void TestFunc2(int a, int b = 1, int c = 0)
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
/*TestFunc();
TestFunc(10);*/
/*TestFunc1();
TestFunc1(10, 20, 30);
TestFunc1(10);
TestFunc1(10, 20);*/
TestFunc2(10);
TestFunc2(10,20);
TestFunc2(10,20,30);
system("pause");
return 0;
}
#endif
#if 0
//函数重载
int ADD(int left, int right)
{
return left + right;
}
double ADD(double left, double right)
{
return left + right;
}
char ADD(char left, char right)
{
return left + right;
}
//形参列表不同(个数、类型、顺序)
void Test()
{}
void Test(int a)
{}
void Test(double a)
{}
void Test(int a, double b)
{}
void Test(double a, int b)
{}
int main()
{
//cout << ADD(1, 2) << endl;
//cout << ADD(1.1, 2.2) << endl;
//cout << ADD('1', '2') << endl;//ASCLL码相加
system("pause");
return 0;
}
#endif
#if 0
//引用
void Swap(int& left,int& right)
{
int tmp = left;
left = right;
right = tmp;
}
//如果不需要通过形参修改实参的值,最好的方法是在形参引用前加上const修饰
int TestFunc(const int& a)
{
return a;
}
//如果用引用作为函数的返回值类型,不能返回函数栈上的空间
//如果一定要用引用作为返回值,返回的变量生命周期一定要比函数的生命周期长
int& Test()
{
int x = 1;
return x;
}
int main()
{
int a = 10;
int b = 20;
const int c = 30;
int& ra = a;//引用在定义时必须初始化
int& rra = a;//一个变量可以有多个引用
//int& ra = b; //引用一旦引用了一个实体,就不能再引用其他实体
cout << &a << endl;//共用同一块内存空间,所以地址都相同
cout << &ra << endl;
cout << &rra << endl;
const int& rc = c;//引用常量实体必须加const修饰
const int& rd = 10;
double e = 12.34;
const int& re = e;//引用类型与引用实体不同时加const可以通过编译,此时编译器会为引用创建一个临时变量,这个临时变量具有常属性
e = 100;
ra = 20;//一般情况下,因为引用与实体共用同一块内存空间,所以改变引用的值也就是改变了实体的值
rra = 30;
a = 10;
b = 20;
Swap(a, b);
cout << TestFunc(a) << endl;
int& rx = Test();
cout << rx << endl;//10
cout << rx << endl;//随机值 因为第一次输出时Test函数中x所指向的栈上空间已经被cout压栈覆盖了
system("pause");
return 0;
}
#endif
//传值、传地址、传引用效率比较:
#include
struct A
{
int array[10000];
};
void TestFunc(A& a)
{}
void TestRefPtr()
{
A a;
size_t start = GetTickCount();
for (size_t i = 0; i < 1000000; i++)
TestFunc(a);
size_t end = GetTickCount();
cout << end - start << endl;
}
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
int &ra = a;
ra = 20;
TestRefPtr();
ra++;
pa++;
char c = 'a';
char& rc = c;
char* pc = &c;
cout << sizeof(rc) << endl;
cout << sizeof(pc) << endl;
system("pause");
return 0;
}