游戏开发和网站开发哪个好玩,国外服务器租用价格,公司网站怎么优化,笨笨网站建设专家return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
return语句有两种形式
return;return expression;
无返回值函数
没有返回值的return语句只能用在返回类型是void的函数中。
返回void的函数不要求必须有return语句#xff0c;因为这类函数的最后一句…return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
return语句有两种形式
return;return expression;
无返回值函数
没有返回值的return语句只能用在返回类型是void的函数中。
返回void的函数不要求必须有return语句因为这类函数的最后一句后面会隐式地执行return.
也就是说下面这两种写法是等价的
void A()
{
return;
}void A()
{
}
一个返回类型是void的函数也能使用return语句的第二种形式不过此时return语句的expression必须是另一个返回void的函数。强行令void函数返回其他类型的表达式将产生编译错误。
void B(...)
{;
}
void A()
{return B();//B的返回类型是void所以可以
}
int B(...)
{;
}
void A()
{return B();//B的返回类型不是void所以不可以
} 有返回值函数
return 语句的第二种形式提供了函数的结果。
只要函数的返回类型不是 void则该函数内的每条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同或者能隐式地转换成函数的返回类型。 尽管C无法确保结果的正确性但是可以保证每个return语句的结果类型正确。也许无法顾及所有情况但是编译器仍然尽量确保具有返回值的函数只能通过一条有效的return语句退出。
例如
int A()
{
return;//编译器会报错
}
第一段代码的return语句是错误的因为它没有返回值编译器能检测到这个错误。
int A(int w)
{int i;
for(i0;i10;i)
{
if(iw)
return i;
}
}错误是函数在for 循环之后没有提供return语句。在上面的程序中如果i!w则函数在执行完for循环后还将继续其执行过程显然应该有一条return语句专门处理这种情况。编译器也许能检测到这个错误也许不能如果编译器没有发现这个错误则运行时的行为将是未定义的。 在含有return语句的循环后面应该也有一条return语句如果没有的话该程序就是错误的。很多编译器都无法发现此类错误. 值是如何被返回的
返回值
返回一个值的方式和初始化一个变量或形参的方式完全一样返回的值用于初始化调 用点的一个临时量该临时量就是函数调用的结果。
必须注意当函数返回局部变量时的初始化规则。
例如我们书写一个函数给定计数值、单词和结束符之后判断计数值是否大于1如果是返回单词的复数形式如果不是返回单词原形
//如果ctr的值大于1返回word的复数形式
string make_plural(size_t ctr, const string word, const string ending)
{
return (ctr 1) ? word ending : word;
} 该函数的返回类型是string意味着返回值将被拷贝到调用点。因此该函数将返回word 的副本或者一个未命名的临时string对象该对象的内容是word和ending的和。 我们再看一个例子
int A()
{
int a9;
return a;
}...
int bA(); 函数返回值是int类型执行return语句的同时把a的值拷贝到一个临时的int变量上面然后销毁局部变量a然后再将这个临时变量赋给调用对象——变量b 返回引用
同其他引用类型一样如果函数返回引用则该引用仅是它所引对象的一个别名。
举个例子来说明假定某函数挑出两个string形参中较短的那个并返回其引用
//挑出两个string对象中较短的那个返回其引用
const string shorterstring(const string sl, const string s2)
{
return sl.size() s2.size() ? sl:s2;
}
其中形参和返回类型都是const string的引用不管是调用函数还是返回结果都不会真正拷贝string对象。
不要返回局部对象的引用或指针
函数完成后它所占用的存储空间也随之被释放掉。因此函数终止意味着局部变量的引用将指向不再有效的内存区域
//严重错误这个函数试图返回局部对象的引用
const int A()
{
int a;
if(a3)
return a;
else
return 4;} 上面的两条return 语句都将返回未定义的值也就是说试图使用返回值将引发未定义的行为对于第一条return语句来说显然它返回局部对象的引用。对于第二条return语句来说它返回了一个临时的int值。 当函数结束时临时对象占用的空间也就随之释放了这两条return语句都指向了不可再用的内存空间。 有人可能好奇的去试了一下
int AA()
{int a 9;return a;
}int main()
{int g AA();
coutgendl;
}
发现打印出了9啊。这是为什么呢
在这个代码中函数AA返回一个对局部变量a的引用。但是这里有一个重要的问题局部变量a在函数返回后会被销毁这意味着返回的引用指向了一个不再存在的变量。这通常会导致未定义行为因为程序可能会尝试访问已经被释放的内存。
在大多数编译器和设置下你可能会看到g的值是9但这完全是偶然的。这是因为局部变量a在函数返回后可能仍然保留在内存中的那个位置并且它的值没有被其他数据覆盖。但是这并不意味着这样的代码是安全的或可预测的。
现在来详细解释一下函数返回的过程 当AA函数被调用时它会在栈上分配空间来存储局部变量a并给它赋值为9。然后AA函数返回这个局部变量的引用。这本质上是一个内存地址。在main函数中你试图将这个引用赋值给一个整数变量g。但是因为引用本质上是一个地址所以这里发生了隐式解引用即g被赋予了地址指向的值即9。最后你打印出g的值它看起来是9。 然而由于前面提到的未定义行为这个程序可能会在不同的编译器、不同的优化级别或不同的运行环境下表现出不同的行为。因此这种代码是不安全的应当避免使用。 int* B()
{
int b;
int*cb;
return c;
}
同样返回局部对象的指针也是错误的一旦函数完成局部对象被释放指针将指向一个不存在的对象。
同样的道理我们也来像上面一样来看看下面这样的程序
#includeiostream
using namespace std;int* B()
{int b5;int* c b;return c;
}int main()
{int *g B();cout *g endl;
}
我们发现打印出了5这又是为啥呢 在这段代码中函数B返回一个指向局部变量的指针。局部变量b在函数B的栈帧中分配并在函数返回后不再存在。然而由于你返回了指向它的指针你仍然可以通过这个指针访问它的内存位置尽管这是未定义行为因为b已经不再是一个有效的对象。 这里是为什么这段代码打印出5的原因 当调用函数B时它在栈上分配了局部变量b并将其初始化为5。接下来创建了一个指向b的指针c。函数B返回这个指针c即返回了b的地址。在main函数中你接收了这个返回的指针并将其存储在g中。通过解引用g即*g你访问了之前b所在的内存位置并打印出了存储在那里的值即5。 然而这种行为是不安全的因为它依赖于局部变量的栈内存位置在函数返回后不会被改变。在实际编程中这种做法是不推荐的因为它可能导致不可预测的行为和程序崩溃尤其是在更复杂的程序或者存在并发操作时。 正确的做法是避免返回指向局部变量的指针或者确保在返回指针之前所指向的数据已经分配在了一个合适的持久存储位置比如动态分配的内存使用new或malloc等。 关于函数返回过程 函数B执行完毕后它的栈帧会被销毁包括其中的局部变量。然而返回的指针c或者说它所指向的地址并不会自动失效。当main函数接收这个指针并尝试访问它时它实际上是在访问一个已经被销毁的栈帧中的内存位置。如果这个内存位置在函数返回后没有被其他函数调用覆盖那么你可能仍然能够看到原来的值就像在这个例子中一样。但是这是不可预测的并且是不安全的。 因此尽管这段代码在某些情况下可能看起来能够正常工作但它实际上包含了严重的错误和潜在的风险。 返回类类型的函数和调用运算符 和其他运算符一样调用运算符也有优先级和结合律。用运算符的优先级与点运算符和箭头运算符相同并且也符合左结合律。 因此如果函数返回指针、引用或类的对象我们就能使用函数调用的结果访问结果对象的成员。 例如我们可以通过如下形式得到较短string对象的长度
// 调用string 对象的size成员该string对象是由shorterString函数返回的auto sz shorterString(si, s2).size(); 因为上面提到的运算符都满足左结合律所以 shorterString 的结果是点运算符的左侧运算对象点运算符可以得到该string对象的size成员size又是第二个调用运算符的左侧运算对象。
引用返回左值 函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值其他返回类型得到右值。可以像使用其他左值那样来使用返回引用的函数的调用特别是我们能为返回类型是非常量引用的函数的结果赋值 #includeiostream
using namespace std;int A(int a)
{
return a;
}
int main()
{
int a9;
coutaendl;
A(a)99;//返回值当左值用
coutaendl;
} 把函数调用放在赋值语句的左侧可能看起来有点奇怪但其实这没什么特别的。
返回值是引用因此调用是个左值和其他左值一样它也能出现在赋值运算符的左侧。
如果返回类型是常量引用我们不能给调用的结果赋值这一点和我们熟悉的情况是一样的 列表初始化返回值 C11新标准规定函数可以返回花括号包围的值的列表。类似于其他返回结果此处的列表也用来对表示函数返回的临时量进行初始化。如果列表为空临时量执行值初始化否则返回的值由函数的返回类型决定。 举个例子回忆6.2.6节(第198页)的error msg函数该函数的输入是一组可变数量的string实参输出由这些string对象组成的错误信息。在下面的函数中我们返回一个vector对象用它存放表示错误信息的string对象
vectorstring process(){//...
// expected 和actual是string对象
if (expected.empty())
return {}; // 返回一个空vector对象
else if (expected actual)
return {functionX,okay}; // 返回列表初始化的vector对象
else
return {functionx, expected, actuall};} 第一条return语句返回一个空列表此时process函数返回的vector对象是空的。如果expected不为空根据expected和actual是否相等函数返回的vector对象分别用两个或三个元素初始化。
如果函数返回的是内置类型则花括号包围的列表最多包含一个值而且该值所占空间不应该大于目标类型的空间。如果函数返回的是类类型由类本身定义初始值如何使用。
int C()
{return{ 1 };
}
主函数main的返回值 之前介绍过如果函数的返回类型不是void那么它必须返回一个值。 但是这条规则有个例外我们允许main函数没有return语句直接结束。 如果控制到达了main函数的结尾处而且没有return语句编译器将隐式地插入一条返回0的return语句。 返回数组指针
因为数组不能被拷贝所以函数不能返回数组。不过函数可以返回数组的指针或引用。虽然从语法上来说要想定义一个返回数组的指针或引用的函数比较烦琐但是有一些方法可以简化这一任务 其中最直接的方法是使用类型别名 typedef int arrT[10]; //arrT是一个类型别名它表示的类型是含有10个整数的数组
using arxT int[10] //arrT的等价声明
artT* func(int i); //func 返回一个指向含有10个整数的数组的指针 其中arrT是含有10个整数的数组的别名。因为我们无法返回数组所以将返回类型定义成数组的指针。因此func函数接受一个int实参返回一个指向包含10个整数的数组的指针。 声明一个返回数组指针的函数 要想在声明func时不使用类型别名我们必须记住数组的维度应跟随在要定义的数组名之后 int arr[10] // arr是一个含有10个整数的数组
int *p1[10] // p1是一个含有10个指针的数组
int (*p2)[10] arr; // p2是一个指针它指向含有10个整数的数组 和这些声明一样如果我们想定义一个返回数组指针的函数则数组的维度必须跟在函数名字之后。然而函数的形参列表也跟在函数名字后面且形参列表应该先于数组的维度。 因此返回数组指针的函数形式如下所示 Type (*function (parameter_list))[dimension] 类似于其他数组的声明Type表示元素的类型dimension 表示数组的大小。(*function(parameter_list))两端的括号必须存在就像我们定义p2时两端必须有括号一样。如果没有这对括号函数的返回类型将是指针的数组。 举个具体点的例子下面这个func函数的声明没有使用类型别名 int (*func(int i))[10]; 可以按照以下的顺序来逐层理解该声明的含义 func(int i)表示调用func函数时需要一个int类型的实参。(*func(int i))意味着我们可以对函数调用的结果执行解引用操作。(*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组。int (*func(int i))[10]表示数组中的元素是int类型。 使用尾置返回类型
在C11新标准中还有一种可以简化上述func声明的方法就是使用尾置返回类。
任何函数的定义都能使用尾置返回但是这种形式对于返回类型较复杂的函数最有效比如返回类型是数组的指针或者数组的引用。
尾置返回类型跟在形参列表后面并以一个符号开头。
为了表示函数真正的返回类型跟在形参列表之后我们在本应该出现返回类型的地方放置一auto:
// func 接受一个int 类型的实参返回一个指针该指针指向含有10个整数的数组
auto func(int i) - int(*) [10]
因为我们把函数的返回类型放在了形参列表之后所以可以清楚地看到func函数返回的是一个指针并且该指针指向了含有10个整数的数组。
我们再看一个例子
#includeiostream
using namespace std;
auto BB() - int
{int a 9;return a;
}
auto BBB() - int*
{int a 9;return a;
}
int main()
{cout BB() endl;cout BBB() endl;
} 使用 decltype
还有一种情况如果我们知道函数返回的指针将指向哪个数组就可以使用decltype关键字声明返回类型。 例如下面的函数返回一个指针该指针根据参数i的不同指向两个已知数组中的某一个
int odd[] {1, 3,5,7,9}
int even[] {0, 2, 4, 6, 8};
//返回一个指针该指针指向含有5个整数的数组
decltype (odd) *arrPtr(int i){return (i %2) ? odd: even;// 返回一个指向数组的指针}
使用关键字 decltype 表示它的返回类型是个指针并且该指针所指的对象与odd的类型一致。
因为odd是数组所以arrPtr返回一个指向含有5个整数的数组的指针。
有一个地方需要注意decltype并不负责把数组类型转换成对应的指针所以decltype的结果是个数组要想表示arrPt返回指针还必须在函数声明时加一个*符号
我们再看一个例子
#includeiostream
using namespace std;int a 9;
decltype(a) BB()
{int a 9;return a;
}int main()
{
coutBB()endl;//结果是9
}