-
黑马程序员——C语言基础06—数组
2015-11-11 12:24:09——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-一、一维数组1、什么是数组? 数组是一组由相同数据类型组成的若干...也可以包括常量表达式,但是不能包含变量(被调用的函数中,作为参数除外——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
一、一维数组
1、什么是数组?
数组是一组由相同数据类型组成的若干有序数据的集合。
数组根据数组名下标的个数分为一维数组和多维数组。2、定义一维数组
1)一般形式:
类型符 数组名[常量表达式];
常量表达式用来表示元素的个数,即数组长度。也可以包括常量表达式,但是不能包含变量(被调用的函数中,作为参数除外)。
int a[3+5] 是合法的,int a[n]是不合法的。int n; scanf("%d",&n); int a[n]; //企图在程序中临时输入数组的大小
2)引用一维数组元素
数组名[下标]int a [10];// 这里但a[10]表示的是定义数组时制定数组包含10个元素 int t = a[6];//而这里的a[6]表示引用a数组中序号为6的元素。 //定义数组时用到的 数组名 [常量表达式]和引用数组元素时用的‘数组名 [下标]’形式相同,但含义不同。
3、一维数组的初始化
1)初始化
int a[5] = {2,3,4,5,6}; int b[10]={1,23,23,4} //给部分元素赋值,未赋值部分自动填补为0 //如果是字符型数组,则填补为‘\0’ int[] = {2,3,4} //是合法的,不指定数组长度,但实际长度是由大括号中的赋值个数决定的,等价于int[3]={2,3,4}; char c[10]={'L','O','V','E'}; //字符数组的初始化
2)遍历一维数组
//逆序 int main( ) { int a[5]={45,56,54,53,23}; for (int i=4; i>=0; i--) { printf("%d\n",*(a+i)); } return 0; }
//数组内元素最大值 #include <stdio.h> int max(int a, int b) { return a>b?a:b ; } int main(int argc, const char * argv[]) { int Num[5]={34,56,34,12,23}; int max1=Num[0]; for (int i=0;i<5; i++) { for 循环遍历 printf("Num[%d]=%d\n",i,*(Num+i)); max1=Num[i+1]>max1 ? Num[i+1] : max1; //三目运算 } printf("数组的最大值为%d\n",max1); return 0; }
4、一维数组的存储方式
1)在一维数组初始化时,也会在计算机内分配存储空间给不同的变量。
2)数组中的每个元素都有自己行对应的地址,他们的地址是连续的,
3)数组内部元素在寻址是是由小地址到大地址定型分配的,靠前的元素分配内存的低地址,而靠 的元素分配内存高的地址。这个与定义变量时候相反。a[5]={34,23,1,4,5}; 元素的地址是连续的,数组名a就是数组第一个元素的地址常量。 &a[0]==a; &a[1]==a+1; &a[2]==a+2; &a[i]==a+i;
字符数组的输入输出有两种方法:
1) 逐个字符输入输出,用%c依次输出每个字符。
2)将整个字符串一次输出,用%s格式符。char c[]={"china"}; printf("%s",c);
5.一维数组与函数
1)数组名当作实际参数时,被调用的函数的行参必须也是同类型;
2)实际参数与行参之间是地址传递,不是值传递。
3)行参数组的长度可以与实参数组的长度不一致,系统不会报错,但是运行结果与实际不相同。void test(int arr[]){ } ... int main(){ int b[3]={2,3,4}; test(b); //实参和行参共享a[0]的地址。 .... }
6.实例应用
1、用数组处理Fibonacci数列问题 #include<stdio.h> int main(){ int i; int f[20]={1,1};//对最前面两个元素f[0]和f[1]初始化为1 for(i=2;i<20;i++) f[i]=f[i-2]+f[i-1];//先后求出f[2]~f[19]的值 for(i=0;i<20;i++) { if(i%5==0) printf("\n");//控制每输出5个数后换行 printf("%12d",f[i]);//遍历输出 } printf("\n"); return 0; 2、用冒泡排序法排序一组长度为10的无序数列 “大数下沉” #include<stdio.h> void main() { int i,j,temp; int a[10]; printf("请输入十个整数:"); for(i=0;i<=9;i++) scanf("%d",&a[i]); for(i=0;i<9;i++) for(j=9;j>i;j--) { if(a[j]<a[j-1]) { temp=a[j]; a[j]=a[j-1]; a[j-1]=temp; } }/* for(j=0;j<9-i;j++) { if(a[j]>a[j+1]) { temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } }大的气泡往下沉,小的气泡往上浮!!!注意:是a[j-1]还是a[j+1]; 深刻了解!!! */ for(i=9;i>=0;i--) printf("%4d",a[i]); } 3、用选择排序法 #include<stdio.h> void main() { int i,j,min,temp; int a[10]; printf("请输入十个整数:"); for(i=0;i<=9;i++) scanf("%d",&a[i]); for(i=0;i<9;i++) { min=i; for(j=i+1;j<=9;j++) { if(a[min]>a[j]) { min=j; } temp=a[j]; a[j]=a[min]; a[min]=temp; } } for(i=0;i<=9;i++) printf("%4d",a[i]); } 通过这两个程序,可以发现他们的编程还是有些区别的,但是总结下: 相同点: 1》都要通过n-1组排出具有n个数的顺序; 2》都是通过逐个相比,比出最值的; 不同点: 1》冒泡法,顾名思义就是把小的泡冒到上面,大的泡沉到下面,最值在中间和其他的值交换; 2》而选择法,是假定了一个最值,所以最小和其他的值的交换就发生在假定最值的地方;
4、字符数组:输入一行字符,统计其中有多少个单词,单词之间用空格分隔开; #include<stdio.h> int main(){ char string[81]; int i,num=0,word=0; char c; gets(string); for(i=0;(c=string[i])!='\0';i++)//输入一个字符串给字符数组string if(c==' ') word=0;//只要字符不是‘\0’就继续执行循环 else if(word==0){//如果不是空格字符且word原值为0 word=1;//是word置1 num++;//num 累加1,表示增加一个单词 } printf("这行一共有%d个单词. \n" ,num);//输出单词数 return 0; }
二、二维数组
1. 二维数组的定义
1) 二维数组常称为矩阵。
2) 类型说明符 数量名[常量表达式][常量表达式]
例如 float a[3][4];3)二维数组是特殊的一维数组,它的每一个元素又是包含n个元素的一维数组。
4)二维数组中元素排列的顺序是按行存放的,在内存中存储也是由小到大进行存储。
2.二维数组的引用及初始化
1)引用表现形式
数组名[m][n];
用来表示第m行的第n个元素。
2)初始化:int[3][2]={1,2,3,4,5,6}; int[3][2]={{2,3},{4,5},{6,7}}; int[][2]={{45,54},{23,32},{12,21}} //第一维的下标可以省略,但是第二维的不可省略。 int[3][4]={1} //部分初始化 char c[2][3]={{'a','b','C'}{'d','e'}};//二维字符数组
每一个都是双下标变量,可以被赋值。
3.二维数组的存储
int a[3][2]中;a[0]可以看成{a[0][0] a[0][1]}数组的数组名,也是数组a的第一个元素(这个是从一位数组的角度考虑),所以 a[0]==&a[0][0],a==&a[0];而a[0][0]又是整个数组的第一个元素,所以
a== &a[0][0];所以:a ==&a[0]==&a[0][0]==a[0]; a+1==&a[1]==&a[1][0]==a[1]; 二维数组的总字节为 sizeof(a)或者 行*列*sizeof(int); 每一行的字节数为sizeof(a[0]); 行数=sizeof a /sizeof (a[0]); 列数=sizeof (a[0])/sizeof(int);
4.二维数组与函数
二维数组元素作为函数参数传递,相当于变量多值传递。
二维数组名作为函数传递,相当于址传递。
二维数组作为函数的行参可以一不写一维的长度5.字符串
1)puts(数组名) 输出字符串的函数
2)gets(数组名) 输入字符串的函数
3)strcat(数组名1,数组名2)字符串连接函数
4)strcpy(数组名1,数组名2)字符串的复制函数
5)strncpy(数组名1,数组名2,2)将数组2中最前面2个字符复制到数组1中,取代数组1中原有的最前面2个字符
6)strmp(数组名1,数组名2)返回0相等 负值是小于 正值是大于
7)strlen(数组名1)测字符串长度的函数,不包括结束符。 -
JavaScript入门教程第3章.ppt
2020-04-30 10:55:07什么是表达式 什么是运算符 运算符的优先级 常量和变量 什么是常量 程序一次运行活动的始末有的数据经常发生改变有的数据从未被改变也不应该被改变常量是指从始至终其值不能被改变的数据JavaScript中的常量类型主要... -
C++程序员应了解的那些事(68)非类型模板参数
2020-11-26 22:28:13※ 而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。 非类型形参的局限: 1.浮点数不可以作为非类型形参,包括float,double。具体原因可能是历史因素,也许未来C++会支持浮点数; 2.类不可以作为...目录
非类型模板参数使用举例:非类型函数模板参数 非类型类模板参数
<3>非类型模板参数的限制(可以是常整数 或者指向外部链接对象的指针(或引用))
模板非类型参数导入:
什么是非类型形参?
模板除了定义类型参数,我们还可以在模板定义非类型参数。
非类型模板参数,顾名思义,模板参数不限定于类型,普通值也可作为模板参数。在基于类型的模板中,模板实例化时所依赖的是某一类型的模板参数, 你定义了一些模板参数(template<typename T>)未加确定的代码,直到模板被实例化这些参数细节才真正被确定。而非类型模板参数,面对的未加 确定的参数细节是指(value),而非类型。当要使用基于值的模板时,你必须显式地指定这些值,模板方可被实例化。
什么是非类型形参?顾名思义,就是表示一个固定类型的常量而不是一个类型。
※ 固定类型是有局限的,只有整形,指针和引用才能作为非类型形参;
※ 而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。①表达式参数有一些限制,可以为整型、枚举、引用或者指针。double m不合法,但是double * pm和double& rm合法。②模板代码不能修改表达式的值,也不能使用使用参数的地址。③实例化模板时,用作表达式参数的值必须是常量表达式。
非类型形参的局限:
1.浮点数不可以作为非类型形参,包括float,double。具体原因可能是历史因素,也许未来C++会支持浮点数;
2.类不可以作为非类型形参;
3.字符串不可以作为非类型形参;
4.整形,可转化为整形的类型都可以作为形参,比如int,char,long,unsigned,bool,short(enum声明的内部数据可以作为实参传递给int,但是一般不能当形参);
5.指向对象或函数的指针与引用(左值引用)可以作为形参。非类型实参的局限:
1.实参必须是编译时常量表达式,不能使用非const的局部变量,局部对象地址及动态对象;
2.非const的全局指针,全局对象/全局变量(下面可能有个特例)都不是常量表达式;
3.由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参 ;
4.非类型模板参数限制——不可以使用内部链接对象 ※
备注:常量表达式基本上是字面值以及const修饰的变量
*************************************************************************************************<例1-整型> const int r = 777; template<int r> void R() { cout << r << endl; } ---------------------- int main() { R<666>(); R<r>(); const int r2 = 888; R<r2>(); int r3 = 999; //局部变量 // R<r3>(); //错误:包含非静态存储持续时间的变量不能用作非类型参数(非const的局部变量) }
<例2-指针> char x1[] = "saaaa";//全局变量 char * x2 = "qweqeq";//全局变量 char * const x3 = "qweqeq";//全局变量 指针常量 (warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings] 修改为:const char *x2 = "qweqeq";) template<typename T, char* x> void X(T t) { cout << t << ", " << x << endl; }; --------------------------------- int main() { X<int, x1>(3); // 这是那个例外 // X<int, x2>(4); // 错误: 应为编译时常量表达式 // X<int, x3>(5); // 错误:非const的全局指针! & 涉及带有内部链接的对象的表达式不能用作非类型参数 char *x4 = "adsas";//局部变量,告警:ISO C++ forbids converting a string constant to 'char*' // X<int, x4>(6);// 错误: 包含非静态存储持续时间的变量不能用作非类型参数 // X<int, "sdfsd">(7);//错误:字符串,浮点型即使是常量表达式也不可以作为非类型实参 }
<例3-整型> template<int a> void A(const char(&p)[a]) { std::cout << "size : " << a << " " << p << std::endl; } ----------------------------------------- int main() { A("hello") ; A<6>("hello"); }
<例4-引用> struct Y {}; Y y; template<const Y& b> struct Z {}; int q = 1; template<const int& q> struct Q {}; -------------------- int main() { Z<y> z; Q<q> q1; }
<例5-数组指针> int b[5] = {11,22,33,44}; template<int(&pa)[5]> void B() { cout << pa[1] << endl; }; --------------------------- int main() { B<b>(); // ok: no conversion }
<例6-函数指针> void f(int a) { cout << "a is "<<a << endl; } template<void(*pf)(int)> void C() { pf(111); }; ---------------------------------- int main() { C<&f>(); // ok: overload resolution selects f(int) }
<例7-模板 模板参数> template<typename T> class N { public: int x; }; // primary template 通用模板 template<class T> class N<T*> { public: long x; }; // partial specialization 偏特化 template< template<typename> class V > class M { public: V<int> y; // uses the primary template V<int*> z; // uses the partial specialization }; ------------------------------------ int main() { M<N> m = M<N>(); //显示地调用内建类型的缺省构造函数 cout << m.y.x << endl; }
< 例8- 字符串&指针 > template<char const* name> class pointerT{ }; pointerT<"testVarChar"> p1;//错误 char a[] = "saaa";;//全局变量 char a2[] = "saaa";;//局部变量,写在main函数里面 char *b = "saaa";//全局变量 char *const c = "saaa";//全局变量,顶层指针,指针常量 pointerT<a> p2;//正确 pointerT<a2> p22;//错误,局部变量不能用作非类型参数 pointerT<b> p3;//错误,error C2975:“pointerT”的模板参数无效,应为编译时常量表达式 pointerT<c> p4;//错误,error C2970: “c”: 涉及带有内部链接的对象的表达式不能用作非类型参数
剖析&注解:
①到底为什么字符串不能作为实参?
答:(1)我们看到上面p1的模板实参是"testVarChar",然而当我们在另一个编译单元(.cpp文件)同样声明这么一个模板实例时,这两个"testVarChar"的地址可能是不同的,编译器传递给模板时就会传递传递不同的地址,从而导致这两个模板实例是两个不同且不兼容的类型。这就是支持字符串的问题所在。
(2)两个内容完全相同的字符串字面常数可能存在两个不同的地址上。
有个方法: extern char const hello[] = "Hello World!"; 这里必须使用关键词 extern,因为 const array 采用内部链接(internal linkage)。②.变量b和c作为模板实参为什么错误不同?
答:(1)首先解释b实参,b在这里看做是一个指针,是一个全局指针,但是他不是一个常量表达式,所以b不对; 我们再看看c,c相比于b对了一个const修饰符,表示这个指针是一个常量。然而const是一个比较特别的关键字,他具有内部链接属性(关于内连接参考博客 理解C++的链接:C++内链接与外链接的意义),也就是说仅在定义这个变量的文件内可见,不会造成不同编译单元的混编时的链接错误。
(2)这个特性对于模板来说可是有问题的,就像问题①所描述的,由于每个编译单元可能都有一个c变量,导致在编译时,实例化多个c,而且c的地址还不同,这就造成两个模板的实例是两个不同且不兼容的类型。
***************************************************************************************************************
非类型模板参数使用举例:非类型函数模板参数 非类型类模板参数
<1>非类型类模板参数
这里我们使用一个新版本的Stack类模板,这类模板的底层容器是一个一维数组,数组的元素类型由模板类型参数typename T指定,而一位数组在初始化时必须指定其大小,这个大小便可通过一个非类型的模板参数int MAXSIZE指定。
template<typename T, int MAXSIZE> class Stack { public: Stack():idx(0){} bool empty() const { return idx == 0;} bool full() const { return idx == MAXSIZE;} void push(const T&); void pop(); T& top(); const T& top() const; private: int idx; T elems[MAXSIZE]; } template<typename T, int MAXSIZE> void Stack<T, MAXSIZE>::push(const T& elem) //定义类模板成员函数的形式 { if (full()) throw std::out_of_range("Stack<>::push(): full stack"); elems[idx++] = elem; } template<typename T, int MAXSIZE> void Stack<T, MAXSIZE>::pop() { if (!empty()) idx--; else throw std::out_of_range("Stack<>::pop(): empty stack") } template<typename T, int MAXSIZE> T& Stack<T, MAXSIZE>::top() { if (empty()) throw std::out_of_range("Stack<>::top(): empty stack"); return elems[idx-1]; } template<typename T, int MAXSIZE> const T& Stack<T, MAXSIZE>::top() const { if (empty()) throw std::out_of_range("Stack<>::top(): empty stack"); return elems[idx-1]; }
①注意上述代码中定义类模板成员函数的形式:
template<typename T, int MAXSIZE> void Stack<T, MAXSIZE>::push(const T& elem) //定义类模板成员函数的形式
客户端程序:
try { Stack<int, 10> int10Stack; Stack<int, 20> int20Stack; int20Stack.push(7); ... } catch(std::exception& ex) { cout << ex.what() << endl; return EXIT_FAILURE; }
②注:每个模板实例都有自己的类型,
int10Stack
和int20Stack
属于不同的类型,这两种类型之间也不存在显示或隐式的类型转换。同样地,也可以为非类型模板参数指定缺省值:
template<typename T, int MAXSIZE = 20> class Stack { ... } 这样在调用时: Stack<int> intStack; // == Stack<int, 20>
<2>非类型函数模板参数
同样地也可以为函数模板定义为非类型参数,如下的函数模板定义一组用于增加特定值的函数:
template<typename T, int VAL> T addValue(const T& x) { return x + VAL; }
当然这样做纯粹为了演示,意义不大,我们可以将非类型参数的类型(这话有点绕)定义为类型参数:
template<typename T, T VAL> T addValue(const T& x) { return x+VAL; }
借助标准模板库(STL),可以将这个函数模板的一个实例传递给集合中的每一个元素,将集合中的每一个值都增加一个预设的值:
std::transform(src.begin(), src.end(), // 原容器(待转换)的起点和终点 dst.begin(), // 目标容器的起点 addValue<std::vector<int>::value_type, 10>); // 操作或者函数(也可以是仿函数)
另外还有一个问题需要注意,
addValue<int, 5>
是一个函数模板的实例化版本,而函数模板的实例化通常被视为用来命名一组重载函数的集合(即使该集合中只有一个元素)。在一些较老的C++标准里,重载函数的集合不能用于模板参数(如本例的transform()
)的演绎。于是,必须显式地将函数模板的实参强制转换为具体的类型:std::transform(ivec.begin(), ivec.end(), dst.begin(), (int(*)(const int& ))addValue<int, 5>);
一个完整的演示程序如下:
int arr[] = {1, 2, 3, 4, 5}; vector<int> src(arr, arr+5), dst(5); typedef vector<int>::value_type value_type; transform(src.begin(), src.end(), dst.begin(), (value_type (*)(const value_type&)addValue<value_type, 5>); copy(dst.begin(), dst.end(), ostream_iterator<value_type>(cout, " "));// ostream_iterator 在<iterator>的std命名空间中
ostream_iterator效果示例:
#include<functional> #include<iterator> #include <iostream> int main() { int arr[] = {1, 2, 3, 4, 5}; vector<int> src(arr, arr+5), dst(5); typedef vector<int>::value_type value_type; copy(src.begin(), src.end(), std::ostream_iterator<value_type>(cout, "*")); } 输出: 1*2*3*4*5*
<3>非类型模板参数的限制(可以是常整数 或者指向外部链接对象的指针(或引用))
非类型模板参数是有类型限制的。一般而言,它可以是常整数(包括enum枚举类型)或者指向外部链接对象的指针(或引用)。
//①浮点数和类对象(class-type)不允许作为非类型模板参数: template<double VAL> // ERROR: 浮点数不可作为非类型模板参数 double process(double v) { return v * VAL; } template<std::string name> // ERROR:类对象不能作为非类型模板参数 class MyClass { }
//②稍作变通,我们即可使编译通过: template<double* PVAL> double process(const double& x) { return x * (*PVAL); } template<const char* name> class MyClass { ... }
③这样可顺利通过编译,但如果想在当前文件中使用这两个模板,还需要动一些手脚: double val = 10; double res = process<&val>(20); // ERROR: 表达式必须含有常量值 MyClass<"hello"> x; // ERROR: 模板参数不能引用非外部实体 const char* s = "hello"; MyClass<s> x; // ERROR: 表达式必须含有常量值
④这里点出另外一点注意事项,也就是非类型模板参数的限制,非类型模板参数可以是指针,但该指针必须指向外部链接对象。还记得在
A.cpp
中如何引用B.cpp
中的全局变量吗,在A.hpp
中使用extern
关键字对外部变量加以引用。// B.cpp double val = 3.14159265; char str[] = "hello"; // A.hpp extern double val; extern char str[]; // A.cpp #include "A.hpp" double res = process<&val>(10); MyClass<str> x;
非类型模板参数限制——不可以使用内部链接对象
template <char const* name> class MyClass { … }; char const* s = "hello"; MyClass<s> x; // ERROR: s is pointer to object with internal linkage 这里"hello"是个内部链接(internal linkage)对象 但是: template <char const* name> class MyClass { … }; extern char const s[] = "hello"; MyClass<s> x; // OK
①"hello"是字符串常量,因为不是“变量”,所以没有内部、外部链接属性。有内部外部链接属性的是那个s。
②C++规定,有const修饰的变量,不但不可修改,还都将具有内部链接属性,也就是只在本文件可见(这是原来C语言的static修饰字的功能,现在const也有这个功能了)。又补充规定,extern const联合修饰时,extern将压制const这个内部链接属性。于是,extern char cosnt s[]将仍然有外部链接属性,但是还是不可修改的。
③char const* s = "hello";
MyClass<s> x; // ERROR: s is pointer to object with internal linkage
我不知道lz标注的error信息是怎样得到的,在vs2005下的错误信息是:
invalid template argument for 'MyClass', expected compile-time constant expression
主要原因在于template是在编译时就生成的,而s是一个指针变量,它的值是运行时可知。
④nontype template parameters要求是在编译或者链接时值必须是可知的。对于内部链接对象来说,对于其他编译单元不可见,而在编译期至少模板的实例化是共享的,但是如果内部链接对象可以作为template argument(模板实参)会发生语义上的错误。例如字符串变量(如“abcx”),它传递给模板的是个指针,而非它的值("abcx"),况且如果还有另外一个"abcx"的话,也是传递的是地址,并且这两个地址从理论上说是不相同的(即使某些编译器会做优化,让其相同)。☆但同时我们要说两个字符串变量的值是相同的,应该共享一个类的定义(注:这是从我们使用者的角度来看),而实际上编译器无法以值的方式传递这种常量,而以地址的方式传递常量。这样如果编译器生成了不同的类的实现,这就违法了模板给我的信息(相同值的非类型模板参数生成的对象是共享一个模板实例的类的),因此编译器不应该生成相应的类的实现。
-
C++ 11 右值引用和移动语义的实现
2019-11-12 22:40:46相反,右值就是不能应用地址运算符&的表示数据的表达式,包括字面常量,x+y,非引用的返回值。 什么是左值引用,什么是右值引用? 我们常说的C++的引用,大部分时候指的就是左值引用,符号是&, 比如 ...什么是左值,什么是右值?
左值就是程序能获得其地址的表示数据的表达式,包括变量,const常量,解除引用的指针。
相反,右值就是不能应用地址运算符&的表示数据的表达式,包括字面常量,x+y,非引用的返回值。
什么是左值引用,什么是右值引用?
我们常说的C++的引用,大部分时候指的就是左值引用,符号是&,
比如 int a=10;int &b=a; 其中,b就是a的引用,可以理解为别名。
右值引用符号是&&,
比如 int &&a = 10; 其中,a就是10的右值引用。&10是非法的,但是&a却是合法的。
移动语义和右值引用的关系
移动语义对降低C++构造和析构的开销有重要的意义,减少了传值、返回值过程中的资源拷贝。
C++移动语义的实现,正是基于右值引用。
传值和返回值的代码开销在哪里?
请看下面这段代码;
#include <iostream> using std::cin; using std::cout; using std::endl; struct Person { Person(const char* p) { cout << "constructor" << endl; } Person(const Person& p) { cout << "copy constructor" << endl; } const Person& operator=(const Person& p) { cout << "operator=" << endl; return *this; } ~Person() { cout << "destructor" << endl; } }; Person getAlice() { Person p("alice"); // 对象创建。调用构造函数,一次 new 操作 return p; // 返回值创建。调用拷贝构造函数,一次 new 操作 // p 析构。一次 delete 操作 } int main() { cout << "______________________" << endl; Person a = getAlice(); // 对象创建。调用拷贝构造函数,一次 new 操作 // 返回值析构,一次 delete 操作 // 当前步骤合共 3次构造,2次析构 cout << "______________________" << endl; a = getAlice(); // 对象创建。调用拷贝构造函数,一次 new 操作 // 返回值析构,一次 delete 操作 // 当前步骤合共 3次构造,2次析构 cout << "______________________" << endl; return 0; // a 析构。一次 delete 操作 }
在不考虑NVRO(返回值优化)的情况下,上面这段代码的预期过程如注释,总共6次构造,5次析构。
当然了,编译器会进行NVRO(返回值优化),减少构造和析构次数。
不同编译器的NVRO结果是不一样的:
在Visual Studio 2015上面编译运行结果是:
Person a = getAlice(),这一步,getAlice里面p的析构和返回值的构造被优化掉了,相当于a直接用了getAlice()的对象;
a=getAlice(),这一步,没有NVRO优化。
g++(8.2.0)优化程度比VS高。
Person a = getAlice(),这一步,getAlice() 里面p的析构,返回值的构造和析构,a的拷贝构造都被优化掉了;
a=getAlice(),这一步,NVRO优化程度比赋初值操作的低,
getAlice() 里面p的析构和返回值的构造被优化掉了,相当于a直接用了getAlice里面的对象;
上面的代码还能优化吗?
可以。
通过移动语义,可以把拷贝构造函数改写成移动构造函数;或者就是另外写一个移动构造函数,实现重载。参考[4]
使用std::move相当于显式使用移动语义。std::move()实际上是static_cast<T&&>()的简单封装。
用右值引用实现移动语义,从而优化拷贝构造函数
参见以下代码和注释
// 基于左值引用的拷贝构造函数 //(参数p设置const属性,不允许直接取用参数p的指针成员,这是为了拷贝构造函数既能接受左值参数,也能接受右值参数) //(不设置const属性也行,但是就不能用右值(getAlice的返回值)进行拷贝构造得到新的对象了。) const Person& operator=(const Person& p) { cout << "operator=" << endl; delete[] name; int len = strlen(p.name) + 1; name = new char[len]; memcpy(name, p.name, len); //左值引用的拷贝构造,会有一次申请内存和数据拷贝 return *this; } // 基于右值引用的拷贝构造函数 //(不需要const了,那么就可以直接取用参数p的指针成员,且可以在取用后将p的指针成员置为nullptr,这样该块内存就不会被析构了) const Person& operator=(Person&& p) { cout << "operator=" << endl; delete[] name; name = p.name; //直接取用 p.name = nullptr; //置空使得系统无法将该块内存析构掉 //相对比左值引用,右值引用的拷贝构造可以实现更加高效:少了一次内存申请和拷贝。 return *this; }
【参考】
[1]《C++ Primer Plus》,18.1.9,右值引用一节
[2] https://harttle.land/2015/10/11/cpp11-rvalue.html 这篇文章讲的比较易于理解
[3] 如何评价 C++11 的右值引用(Rvalue reference)特性? - Tinro的回答 - 知乎 https://www.zhihu.com/question/22111546/answer/30801982
-
C++面试经
2019-07-15 10:23:461.什么是右值引用,跟左值引用有什么区别? 左值:能对表达式取地址...右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。 包括字面常量(用引号引的字符串除外,它们由其地址表示)和...1.什么是右值引用,跟左值引用有什么区别?
左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
可被引用的数据对象,可通过地址访问它们,常规变量和const变量都可视为左值,但是常规变量是可修改的左值,const变量属于不可修改左值。
右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
包括字面常量(用引号引的字符串除外,它们由其地址表示)和包含多项的表达式、返回值的函数(条件是该函数返回的不是引用)。
右值引用:这种引用可指向右值,使用&&声明,将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址。
右值引用的主要目的之一是实现移动语义。右值引用是不具名(匿名)变量的别名。
左值引用:使用&声明的引用。左值引用是具名变量值的别名。2.说一下引用和指针?
引用是已定义的变量的别名,它们指向相同的值和内存变量。主要用途是用作函数的形参和返回值类型,通过将引用变量用作参数,
函数可以直接使用原始数据,而不是其副本。
指针是一个变量,并指向一块内存,他的内容是所指内存的地址。引用是某块内存的别名,引用不改变指向。
共同点:
1)指针和引用作为形参时,可以直接修改作为实参传递过来的调用函数的变量,而不是副本。
区别:
1)引用必须在声明时初始化,引用不能为空;指针可以先声明再赋值,指针可以为空。
2)指针可以重复赋值;但是引用不能重复赋值,一旦引用重复赋值,将不能起到“别名”的作用,只是简单的改变初始化对象的内容化。
3)指针在使用过程中可能需要使用解除引用运算符*。
4)内存分配上:指针时变量需要占用内存;而引用不需要。
5)"sizeof(引用)"得到的是所指向的变量(对象)的大小;而"sizeof(指针)"得到的是指针本身的大小,即4个字节。
6)指针有多级;引用只能是一级。
7)可以有const指针;没有const引用。
8)指针和引用的自增(++)运算意义不一样
指针自加,比如 int a[2] = {0,10} ;int *pa =a;
pa++表示指针往后移动一个int的长度。指向下一个内存地址。及pa从指向a[0]变成指向a[1]
引用是值++;比如b是引用a[0]的,++表示a[0]的值++从0变为1;3.求二进制中1的个数,输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
class Solution { public: int NumberOf1(int n) { //方法一:从n的2进制形式的最右边开始判断是不是1 即和1相与,然后n不断右移。 //即可得到n的2进制形式的1的个数。 //如果n是负数,负数右移时,在最高位补得是1,直接while循环会导致死循环,可以将最高位的符号位1变成0,也就是n & 0x7FFFFFFF /*int flag = 1; int count = 0 ; if(n < 0){ n = n &0x7FFFFFFF; count++; } while(n){ count+=n & flag; n = n >>1; } return count;*/ //方法二:用1(1自身左移运算,其实后来就不是1了)和n的每位进行位与,来判断1的个数 /*int flag = 1; int count = 0; while(flag){ if(n & flag) count++; flag = flag<<1; } return count;*/ //方法三:如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0, //原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。 //举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。 //减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011. //我们发现减1的结果是把最右边的一个1开始的所有位都取反了。 //这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。 //如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。 int count = 0; while(n){ count++; n = n & (n-1); } return count; } };
-
Golang初入编程-踩坑笔记(1)
2020-09-20 21:55:36i++属于独立语句,不能和其他语句写到一起,自己占一行 以下东东需要初始化(开辟空间),包括切片,指针,map(数组声明后,会填充默认值) 函数可返回多个 使用:=声明并赋值 const常量(静态变量)关键字... -
Symbol对象是什么
2020-12-27 09:27:49所以你不能</p><pre><code>new Symbol()</code></pre>来执行; 引用官方翻译,符号是一种特殊的、不可变的数据类型,它可以作为对象属性的标识符使用。符号对象是一个对的符号原始数据类型的隐式... -
C语言数据、数据类型以及运算符
2020-08-08 08:09:21文章目录1、什么是数据1、数据的表现形式1、常量2、变量3、常变量4、标识符2、进制3...值在运行过程中不能改变的量 出现在表达式或者赋值语句中 分类 整型常量(整数):1,2,3 输出%d 实型常量(小数):1.1,2.2 -
数据结构中的抽象数据类型(ADT)
2018-10-13 20:57:12在高级语言中,每个变量、常量及表达式都有各自的取值范围,类型就用来说明变量或表达式的取值范围和所能进行的操作。 在C语言中,按照取值不同,数据类型可分为两类: ①、原子类型:是不可再分解的基本类型... -
右值引用
2018-08-26 23:47:48写在前面 c++ 11 新特性之一:右值引用... 在这种情况下,右值不能被修改的。 那什么是左值什么是右值? C++( 包括 C) 中所有的表达式和变量,要么是左值,要么是右值。 通俗的左值的定义就是非临时对象,那... -
Java面试宝典(传说中的葵花宝典).doc
2020-08-03 10:51:15显然,long类型不符合switch的语法规定,并且不能被隐式转换成int类型,所以,它不能作用于swtich语句中,String类型在JDK7之前不可以,而在JDK7之后可以。 6、short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; ... -
Java基础回顾(2)
2014-11-23 21:26:57本小结主要包括以下知识点: ... 在软件开发的过程中总是强调注释的规范,但是没有一个具体的标准进行说明,通常都是在代码编写规范中简单的描述几句,不能作为一个代码注释检查的标准和依据,做什么都要有一个依据 -
VC++科学计算器的设计V1_4Beta.rar
2010-03-23 04:54:59例如,如果源输入表达式书写错误,并且这种错误解释器内核不能给出自动的纠正策略,则应该报错而立即终止程序;如果解释器内核可以自动纠正之,则应该继续执行,但最好还是应该附加一个警告信息。 以上所述的某种... -
c++ 程序设计
2019-01-20 22:53:372.2.1 什么是常量 2.2.2 数值常量 2.2.3 字符常量 2.2.4 符号常量 2.3 变量 2.3.1 什么是变量 2.3.2 变量名规则 2.3.3 定义变量 2.3.4 为变量赋初值 2.3.5 常变量 2.4 C++的运算符 2.5 算术运算符与算术表达式 2.5.1... -
超级有影响力霸气的Java面试题大全文档
2012-07-18 09:47:04声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,... -
java 面试题 总结
2009-09-16 08:45:34声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其... -
21天学通C++ (中文第五版)
2010-06-23 16:57:03这种情况下,应将两行作为一行输入,不能将它们分开。 本书正文及附录D中的范例代码可从Sams网站下载, 其网址为http://www.samspublishing.com。在文本框“Search'’中输入本书英文版的ISBN(0672327112),... -
C++程序员面试宝典
2013-04-01 13:36:19本书主要内容包括:面试流程及准备、英语面试、电话面试、C/C++语言基础、流程控制、输入/输出、预处理、内存管理、指针、面向对象基础、类、多态、继承、函数、模板与STL、数据结构、软件工程、数据库、操作系统、... -
oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串
2017-05-06 20:26:52而且不能删除当前用户,如果删除的用户有数据对象,那么必须加上关键字cascade。 用法:drop user 用户名 [cascade] 四、 用户权限与角色 1. 权限 Oracle中权限主要分为两种,系统权限和实体权限。 系统权限:... -
语言程序设计课后习题答案
2012-12-27 17:02:372-8 什么叫做表达式?x = 5 + 7是一个表达式吗?它的值是多少? 解: 任何一个用于计算值的公式都可称为表达式。x = 5 + 7是一个表达式,它的值为12。 2-9 下列表达式的值是多少? 1. 201 / 4 2. 201 % 4 3. 201 / ... -
c++ 面试题 总结
2009-09-16 08:44:40一个指向char类型的const对象指针,p不是常量,我们可以修改p的值,使其指向不同的char,但是不能改变它指向非char对象,如: const char *p; char c1='a'; char c2='b'; p=&c1;//ok p=&c2;//ok *p=c1;//error (2)... -
算法笔记.胡凡(带详细书签) PDF 完整版 下载
2018-05-29 14:33:00我们知道,纸质书籍的一个弱点就在于不能像软件一样随时更新内容,但本书采用了与二维码相结合的方式,使得本书变为能够随时更新内容的书籍,读者也可以随时从二 维码中找到勘误。这种作者和读者能够相互沟通的方式... -
C语言入门经典(第4版)--源代码及课后练习答案
2013-02-02 17:18:55Horton拥有丰富的教学经验(教学内容包括C、C++、Fortran、PL/1、APL等),同时还是机械、加工和电子CAD系统、机械CAM系统和DNC/CNC系统方面的专家。IvorHorton还著有关于C、C++和Java的多部入门级好书,如《C语言入门... -
C语言入门经典(第4版)--详细书签版
2013-02-02 17:16:50Horton拥有丰富的教学经验(教学内容包括C、C++、Fortran、PL/1、APL等),同时还是机械、加工和电子CAD系统、机械CAM系统和DNC/CNC系统方面的专家。IvorHorton还著有关于C、C++和Java的多部入门级好书,如《C语言入门... -
最权威的C++教程_C++_Primer_Plus中文第五版+C++_Primer中文第四版(都含源码+习题)(共4分卷)分卷1
2010-06-23 17:33:55章讨论了这些问题,说明了公有继承在什么情况下合适,在什么情况下不合适。 第14章:C++中的代码重用 公有继承只是代码重用的方式之一。本章将介绍其他几种方式。如果一个类包含了另一个类的对象,则称 为包含... -
C#微软培训教材(高清PDF)
2009-07-30 08:51:17本书着重介绍语言本身,比较少涉及应用,不错的入门书,从头讲起,不怕不明白。 <<page 1>> page begin==================== 目 目目 目 录 录录 录 第一部分 C#语言概述.4 第一章 第一章第一章 第... -
C#微软培训资料
2014-01-22 14:10:17第五章 变量和常量 .44 5.1 变 量 .44 5.2 常 量 .46 5.3 小 结 .47 第六章 类 型 转 换 .48 6.1 隐式类型转换 .48 6.2 显式类型转换 .53 6.3 小 结 .56 第七章 表 达 式 .58 7.1 操 作 符 .58 ... -
最权威的C++教程_C++_Primer_Plus中文第五版+C++_Primer中文第四版(都含源码+习题)(共4分卷)分卷2
2010-06-23 17:47:19章讨论了这些问题,说明了公有继承在什么情况下合适,在什么情况下不合适。 第14章:C++中的代码重用 公有继承只是代码重用的方式之一。本章将介绍其他几种方式。如果一个类包含了另一个类的对象,则称 为包含... -
最权威的C++教程_C++_Primer_Plus中文第五版+C++_Primer中文第四版(都含源码+习题)(共4分卷)分卷3
2010-06-23 18:03:39章讨论了这些问题,说明了公有继承在什么情况下合适,在什么情况下不合适。 第14章:C++中的代码重用 公有继承只是代码重用的方式之一。本章将介绍其他几种方式。如果一个类包含了另一个类的对象,则称 为包含... -
PHP基础教程 是一个比较有价值的PHP新手教程!
2010-04-24 18:52:44我当然不清楚ASP/JSP能做些什么。不过明确的是编写那样的代码有多简单,购买它们会有多昂贵以及它们需要多么昂贵和强大的硬件。如果你有什么中立的观点(比如说没有被SUN和Microsoft的百万美金所影响),请顺便通知...
-
基于遗传算法的平面阵列阵列稀疏(matlab程序).zip
-
Spark BulkLoad批量读写Hbase
-
FPGA进阶学习路线.pdf
-
合同证明正版一元付费
-
MySQL 事务和锁
-
PyTorch系列 (二): pytorch数据读取自制数据集并
-
git常用命令
-
朱老师C++课程第3部分-3.6智能指针与STL查漏补缺
-
深入剖析mmap 从三个问题说起
-
用 PyQt5 制作动态钟表
-
使用vue搭建微信H5公众号项目
-
删除本地镜像(5)
-
数据源网站.xlsx
-
MySQL 数据库权限管理(用户高级管理和精确访问控制)
-
占据主动!刘强东微博营销之道.pdf
-
排名第一就是品牌,为何2021年还有人信?
-
骑士人才招聘系统 5.2.6商业版.zip
-
质量保证书-源码
-
C++新特性 引用计数
-
多行元素的文本省略号