(原) c++ 杂
Declaration of variables
C++ is a strongly-typed language, and requires every variable to be declared with its type before its first use. This informs the compiler the size to reserve in memory for the variable and how to interpret its value. The syntax to declare a new variable in C++ is straightforward: we simply write the type followed by the variable name (i.e., its identifier). For example:
int a;
float mynumber;
These are two valid declarations of variables. The first one declares a variable of type int with the identifier a. The second one declares a variable of type float with the identifier mynumber. Once declared, the variables a and mynumber can be used within the rest of their scope in the program.
If declaring more than one variable of the same type, they can all be declared in a single statement by separating their identifiers with commas. For example:
int a, b, c;
This declares three variables (a, b and c), all of them of type int, and has exactly the same meaning as:
int a;
int b;
int c;
To see what variable declarations look like in action within a program, let's have a look at the entire C++ code of the example about your mental memory proposed at the beginning of this chapter:
// operating with variables
#include<iostream>
usingnamespace std;
int main ()
{
// declaring variables:
int a, b;
int result;
// process:
a = 5;
b = 2;
a = a + 1;
result = a - b;
// print out the result:
cout << result;
// terminate the program:
return 0;
}
you should get
4
Don't be worried if something else than the variable declarations themselves look a bit strange to you. Most of it will be explained in more detail in coming chapters.
Initialization of variables
When the variables in the example above are declared, they have an undetermined value until they are assigned a value for the first time. But it is possible for a variable to have a specific value from the moment it is declared. This is called the initialization of the variable.
In C++, there are three ways to initialize variables. They are all equivalent and are reminiscent of the evolution of the language over the years:
c-like initialization/ constructor initialization/ uniform initialization
1. c-like initialization: consists of appending an equal sign followed by the value to which the variable is initialized:
type identifier = initial_value;
For example, to declare a variable of type int called x and initialize it to a value of zero from the same moment it is declared, we can write:
int x = 0;
2. known as constructor initialization:(introduced by the C++ language), encloses the initial value between parentheses (()):
type identifier (initial_value);
For example:
int x (0);
3. known as uniform initialization :, but using curly braces ({}) instead of parentheses (this was introduced by the revision of the C++ standard, in 2011):
type identifier {initial_value};
For example:
int x {0};
All three ways of initializing variables are valid and equivalent in C++.
// initialization of variables
#include<iostream>
usingnamespace std;
int main ()
{
int a=5; // initial value: 5
int b(3); // initial value: 3
int c{2}; // initial value: 2
int result; // initial value undetermined
a = a + b;
result = a - c;
cout << result;
return 0;
}
you should get
6
The for loop
for (initialization; condition; increase) statement;
The goto statement
goto allows to make an absolute jump to another point in the program. This unconditional jump ignores nesting levels, and does not cause any automatic stack unwinding. Therefore, it is a feature to use with care, and preferably within the same block of statements, especially in the presence of local variables.
The destination point is identified by a label, which is then used as an argument for the goto statement. A label is made of a valid identifier followed by a colon (:).
goto is generally deemed a low-level feature, with no particular use cases in modern higher-level programming paradigms generally used with C++. But, just as an example, here is a version of our countdown loop using goto:
// goto loop example
#include<iostream>
usingnamespace std;
int main ()
{
int n=10;
mylabel: //这里就是goto 的 jump 之后的位置
cout << n << ", ";
n--;
if (n>0) goto mylabel;
cout << "liftoff!\n";
}
you should get
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, liftoff!
Arguments passed by value and by reference
In certain cases, though, it may be useful to access an external variable from within a function. To do that, arguments can be passed by reference, instead of by value. For example, the function duplicate in this code duplicates the value of its three arguments, causing the variables used as arguments to actually be modified by the call:
// passing parameters by reference
#include<iostream>
using namespace std;
void duplicate (int& a, int& b, int& c)
{
a*=2;
b*=2;
c*=2;
}
int main ()
{
int x=1, y=3, z=7;
duplicate (x, y, z);
cout << "x=" << x << ", y=" << y << ", z=" << z;
return 0;
}
you should get
x=2, y=6, z=14
To gain access to its arguments, the function declares its parameters as references. In C++, references are indicated with an ampersand (&) following the parameter type, as in the parameters taken by duplicate in the example above.
When a variable is passed by reference, what is passed is no longer a copy, but the variable itself, the variable identified by the function parameter, becomes somehow associated with the argument passed to the function, and any modification on their corresponding local variables within the function are reflected in the variables passed as arguments in the call.
In fact, a, b, and c become aliases of the arguments passed on the function call (x, y, and z) and any change on a within the function is actually modifying variable x outside the function. Any change on b modifies y, and any change on c modifies z. That is why when, in the example, function duplicate modifies the values of variables a, b, and c, the values of x, y, and z are affected.
If instead of defining duplicate as:
/‘duplɪket/ adj/n. 完全一样的东西
void duplicate (int& a, int& b, int& c) // PASSED BY REFERENCE
Was it to be defined without the ampersand signs as:
/'æmpəsænd/
void duplicate (int a, int b, int c) // PASSED BY VALUE
The variables would not be passed by reference, but by value, creating instead copies of their values. In this case, the output of the program would have been the values of x, y, and z without being modified (i.e., 1, 3, and 7).
Recursivity
Recursivity is the property that functions have to be called by themselves. It is useful for some tasks, such as sorting elements, or calculating the factorial of numbers. For example, in order to obtain the factorial of a number (n!) the mathematical formula would be:
n! = n * (n-1) * (n-2) * (n-3) ... * 1
More concretely, 5! (factorial of 5) would be:
5! = 5 * 4 * 3 * 2 * 1 = 120
And a recursive function to calculate this in C++ could be:
// factorial calculator
#include<iostream>
using namespace std;
long factorial (long a)
{
if (a > 1){
cout<<a<<"*";
return (a * factorial (a-1));
}
else{
cout<<1<<"=";
return 1;}
}
int main ()
{
long number = 9;
cout << number << "! = " << factorial (number);
return 0;
}
you should get
9! = 9*8*7*6*5*4*3*2*1=362880
Notice how in function factorial we included a call to itself, but only if the argument passed was greater than 1, since, otherwise, the function would perform an infinite recursive loop, in which once it arrived to 0, it would continue multiplying by all the negative numbers (probably provoking a stack overflow at some point during runtime)
Overloaded functions
In C++, two different functions can have the same name if their parameters are different; either because they have a different number of parameters, or because any of their parameters are of a different type. For example:
#include<iostream>
usingnamespace std;
int operate (int a, int b)
{
return (a*b);
}
string operate (string a, string b)
{
return (a+b);
}
int main ()
{
int x=5,y=2;
string n="Hello ";
string m="Wang";
cout << operate (x,y) << '\n';
cout << operate (n,m) << '\n';
return 0;
}
you should get
10
Hello Wang
Note that a function cannot be overloaded only by its return type. At least one of its parameters must have a different type.
Function templates
/'tɛmplet/ n. 模板
C++ has the ability to define functions with generic types, known as function templates. Defining a function template follows the same syntax than a regular function, except that it is preceded by the template keyword and a series of template parameters enclosed in angle-brackets <>:
template <template-parameters> function-declaration
The template parameters are a series of parameters separated by commas. These parameters can be generic template types by specifying either the class or typename keyword followed by an identifier. This identifier can then be used in the function declaration as if it was a regular type. For example, a generic sum function could be defined as:
template <classSomeType>
SomeType sum (SomeType a, SomeType b)
{
return a+b;
}
It makes no difference whether the generic type is specified with keyword class or keyword typename in the template argument list (they are 100% synonyms in template declarations).
In the code above, declaring SomeType (a generic type within the template parameters enclosed in angle-brackets) allows SomeType to be used anywhere in the function definition, just as any other type; it can be used as the type for parameters, as return type, or to declare new variables of this type. In all cases, it represents a generic type that will be determined on the moment the template is instantiated.
Instantiating a template is applying the template to create a function using particular types or values for its template parameters. This is done by calling the function template, with the same syntax as calling a regular function, but specifying the template arguments enclosed in angle brackets:
name <template-arguments> (function-arguments)
For example, the sum function template defined above can be called with:
x = sum<int>(10,20);
The function sum<int> is just one of the possible instantiations of function template sum. In this case, by using int as template argument in the call, the compiler automatically instantiates a version of sum where each occurrence of SomeType is replaced by int, as if it was defined as:
int sum (int a, int b)
{
return a+b;
}
Let's see an actual example:
// function template
#include<iostream>
using namespace std;
template <classT>
T sum (T a, T b)
{
T result;
result = a + b;
return result;
}
int main () {
int i=5, j=6, k;
string f="HELLO ", g="WANG", h;
k=sum<int>(i,j);
h=sum<string>(f,g);
cout << k << '\n';
cout << h << '\n';
return 0;
}
In this case, we have used T as the template parameter name, instead of SomeType. It makes no difference, and T is actually a quite common template parameter name for generic types.
In the example above, we used the function template sum twice.
with arguments of type int.
2. with arguments of type string.
The compiler has instantiated and then called each time the appropriate version of the function.
Note also how T is also used to declare a local variable of that (generic) type within sum:
T result;
Templates are a powerful and versatile feature. They can have multiple template parameters, and the function can still use regular non-templated types. For example:
// function templates
#include<iostream>
using namespace std;
template <classT, classU>
bool are_equal (T a, U b)
{
return (a==b);
}
int main ()
{
if (are_equal(10,10.0))
cout << "x and y are equal\n";
else
cout << "x and y are not equal\n";
return 0;
}
you should get
x and y are equal
Note that this example uses automatic template parameter deduction in the call to are_equal:
are_equal(10,10.0)
Is equivalent to:
are_equal<int,double>(10,10.0)
Non-type template arguments
The template parameters can not only include types introduced by class or typename, but can also include expressions of a particular type:
// template arguments
#include<iostream>
using namespace std;
template <classT, intN>
T fixed_multiply (T val)
{
return val * N;
}
int main() {
cout << fixed_multiply<int,2>(10) << '\n';
cout << fixed_multiply<int,3>(10) << '\n';
}
you should get
20
30
The second argument of the fixed_multiply function template is of type int. It just looks like a regular function parameter, and can actually be used just like one.
But there exists a major difference: the value of template parameters is determined on compile-time to generate a different instantiation of the function fixed_multiply, and thus the value of that argument is never passed during runtime: The two calls to fixed_multiply in main essentially call two versions of the function: one that always multiplies by two, and one that always multiplies by three. For that same reason, the second template argument needs to be a constant expression (it cannot be passed a variable).
A namespace is an optionally named scope. You declare names inside a namespace as you would for a class or an enumeration. You can access names declared inside a namespace the same way you access a nested class name by using the scope resolution (::) operator. However namespaces do not have the additional features of classes or enumerations. The primary purpose of the namespace is to add an additional identifier (the name of the namespace) to a name
namespace identifier
{
named_entities
}
Where identifier is any valid identifier and named_entities is the set of variables, types and functions that are included within the namespace. For example:
namespace myNamespace
{
int a, b;
}
In this case, the variables a and b are normal variables declared within a namespace called myNamespace.
These variables can be accessed from within their namespace normally, with their identifier (either a or b), but if accessed from outside the myNamespace namespace they have to be properly qualified with the scope operator ::. For example, to access the previous variables from outside myNamespace they should be qualified like:
myNamespace::a
myNamespace::b
Namespaces are particularly useful to avoid name collisions. For example:
// namespaces
#include<iostream>
usingnamespace std;
namespace foo
{
int value() { return 5; }
}
namespace bar
{
constdouble pi = 3.1416;
double value() { return 2*pi; }
}
int main () {
cout << foo::value() << '\n';
cout << bar::value() << '\n';
cout << bar::pi << '\n';
return 0;
}
you should get
5
6.2832
3.1416
In this case, there are two functions with the same name: value. One is defined within the namespace foo, and the other one in bar. No redefinition errors happen thanks to namespaces. Notice also how pi is accessed in an unqualified manner from within namespace bar (just as pi), while it is again accessed in main, but here it needs to be qualified as second::pi.
Namespaces can be split: Two segments of a code can be declared in the same namespace:
namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }
This declares three variables: a and c are in namespace foo, while b is in namespace bar. Namespaces can even extend across different translation units (i.e., across different files of source code).
Namespace aliasing
Existing namespaces can be aliased with new names, with the following syntax:
namespace new_name = current_name;
Pointers and arrays
mypointer = myarray;
注: 这里= 的顺序非常重要
// more pointers
#include<iostream>
usingnamespace std;
int main ()
{
int numbers[5];
int * p;
p = numbers; *p = 10;
p++; *p = 20;
p = &numbers[2]; *p = 30;
p = numbers + 3; *p = 40;
p = numbers; *(p+4) = 50;
for (int n=0; n<5; n++)
cout << numbers[n] << ", ";
return 0;
}
you should get
10, 20, 30, 40, 50,
*p++ // same as *(p++): increment pointer, and dereference unincremented address
*++p // same as *(++p): increment pointer, and dereference incremented address
++*p // same as ++(*p): dereference pointer, and increment the value it points to
(*p)++ // dereference pointer, and post-increment the value it points to
Pointers to pointers
C++ allows the use of pointers that point to pointers, that these, in its turn, point to data (or even to other pointers). The syntax simply requires an asterisk (*) for each level of indirection in the declaration of the pointer:
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
c is of type char** and a value of 8092
*c is of type char* and a value of 7230
**c is of type char and a value of 'z'
void pointers
The void type of pointer is a special type of pointer. In C++, void represents the absence of type. Therefore, void pointers are pointers that point to a value that has no type (and thus also an undetermined length and undetermined dereferencing properties).
This gives void pointers a great flexibility, by being able to point to any data type, from an integer value or a float to a string of characters. In exchange, they have a great limitation: the data pointed by them cannot be directly dereferenced (which is logical, since we have no type to dereference to), and for that reason, any address in a void pointer needs to be transformed into some other pointer type that points to a concrete data type before being dereferenced.
One of its possible uses may be to pass generic parameters to a function. For example:
// increaser
#include<iostream>
usingnamespace std;
void increase (void* data, int psize) // void *data 是空指针
{
if ( psize == sizeof(char) )
{ char* pchar; pchar=(char*)data; ++(*pchar); }// 转化成char 指针
elseif (psize == sizeof(int) )
{ int* pint; pint=(int*)data; ++(*pint); } // 转化成int 指针
}
int main ()
{
char a = 'x';
int b = 1602;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
cout << a << ", " << b << '\n';
return 0;
}
sizeof is an operator integrated in the C++ language that returns the size in bytes of its argument. For non-dynamic data types, this value is a constant. Therefore, for example, sizeof(char) is 1, because char is has always a size of one byte.
Invalid pointers and null pointers
In principle, pointers are meant to point to valid addresses,
the address of a variable
the address of an element in an array.
But pointers can actually point to any address, including addresses that do not refer to any valid element. Typical examples of this are uninitialized pointers and pointers to nonexistent elements of an array:
int * p; // uninitialized pointer (local variable)
int myarray[10];
int * q = myarray+20; // element out of bounds
Neither p nor q point to addresses known to contain a value, but none of the above statements causes an error. In C++, pointers are allowed to take any address value, no matter whether there actually is something at that address or not. What can cause an error is to dereference such a pointer (i.e., actually accessing the value they point to). Accessing such a pointer causes undefined behavior, ranging from an error during runtime to accessing some random value.
But, sometimes, a pointer really needs to explicitly point to nowhere, and not just an invalid address. For such cases, there exists a special value that any pointer type can take: the null pointer value.
This value can be expressed in C++ in two ways:
either with an integer value of zero
the nullptr keyword:
null
int * p = 0;
int * q = nullptr;
int * r = NULL; //defined as an alias of some null pointer constant value (such as 0 or nullptr).
注: The difference between the “Null pointer” and “void pointer”
A null pointer is a value that any pointer can take to represent that it is pointing to "nowhere"
while a void pointer is a type of pointer that can point to somewhere without a specific type.
2.1 one refers to the value stored in the pointer
2.2 the other to the type of data it points to.
Pointers to functions
The typical use of this is for passing a function as an argument to another function. Pointers to functions are declared with the same syntax as a regular function declaration, except that the name of the function is enclosed between parentheses () and an asterisk (*) is inserted before the name:
// pointer to functions
#include<iostream>
usingnamespace std;
int addition (int a, int b)
{ return (a+b); }
int subtraction (int a, int b)
{ return (a-b); }
int operation (int x, int y, int (*functocall)(int,int)) //Pointers to functions
{
int g;
g = (*functocall)(x,y);
return (g);
}
int main ()
{
int m,n;
int (*minus)(int,int) = subtraction;
m = operation (7, 5, addition);
n = operation (20, m, minus);
cout <<n;
return 0;
}
you should get
8
Type aliases (typedef / using)
In C++, there are two syntaxes for creating such type aliases:
using the typedef keyword:
typedef existing_type new_type_name ;
typedefcharC;
typedefunsignedintWORD;
typedefchar * pChar;
typedefcharfield [50];
This defines four type aliases: C, WORD, pChar, and field as char, unsigned int, char* and char[50], respectively. Once these aliases are defined, they can be used in any declaration just like any other valid type:
C mychar, anotherchar, *ptc1;
WORD myword;
pChar ptc2;
field name;
2. using new_type_name = existing_type ;
usingC = char;
usingWORD = unsignedint;
usingpChar = char *;
usingfield = char [50];
The only difference being that
typedef has certain limitationsin the realm of templates that using has not.
using is more generic, although typedef has a longer history and is probably more common in existing code.
(原) c++ 杂的更多相关文章
- IOS 杂笔-9 (MD5 加密)
首先是一段对MD5的简介 *出自一位大牛之手* Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护 ...
- 剥开比原看代码12:比原是如何通过/create-account-receiver创建地址的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码08:比原的Dashboard是怎么做出来的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 正睿OI DAY3 杂题选讲
正睿OI DAY3 杂题选讲 CodeChef MSTONES n个点,可以构造7条直线使得每个点都在直线上,找到一条直线使得上面的点最多 随机化算法,check到答案的概率为\(1/49\) \(n ...
- Atcoder&CodeForces杂题11.7
Preface 又自己开了场CF/Atcoder杂题,比昨天的稍难,题目也更有趣了 昨晚炉石检验血统果然是非洲人... 希望这是给NOIP2018续点rp吧 A.CF1068C-Colored Roo ...
- Codeforces 杂题集 2.0
记录一些没有写在其他随笔中的 Codeforces 杂题, 以 Problemset 题号排序 1326D2 - Prefix-Suffix Palindrome (Hard version) ...
- 贪心/构造/DP 杂题选做Ⅱ
由于换了台电脑,而我的贪心 & 构造能力依然很拉跨,所以决定再开一个坑( 前传: 贪心/构造/DP 杂题选做 u1s1 我预感还有Ⅲ(欸,这不是我在多项式Ⅱ中说过的原话吗) 24. P5912 ...
- 贪心/构造/DP 杂题选做Ⅲ
颓!颓!颓!(bushi 前传: 贪心/构造/DP 杂题选做 贪心/构造/DP 杂题选做Ⅱ 51. CF758E Broken Tree 讲个笑话,这道题是 11.3 模拟赛的 T2,模拟赛里那道题的 ...
- 想写个小说,关于C#的,名字就叫《原Csharp》吧 (第一回 买书未成炁自生 惶惶回屋遇老翁)
以前也有写过一些小说,但是总是写写停停的,因为忙于项目和其他事情,总是耽搁很久(真的是很久)才会继续动两笔,所以我想先在这里以随笔的方式写个关于C#异世界的小故事吧,更新随缘,也稍微能让自己轻松些. ...
随机推荐
- HLS(HTTP Live Streaming)协议之m3u8文件生成方式
HLS(HTTP Live Streaming)是Apple的动态码率自适应技术.主要用于PC和Apple终端的音视频服务.包括一个m3u(8)的索引文件,TS媒体分片文件和key加密串文件. HLS ...
- poj1915 BFS
D - 广搜 基础 Crawling in process... Crawling failed Time Limit:1000MS Memory Limit:30000KB 64bi ...
- 管理员权限dropfiles和copydata小时失败问题
//处理低权限向高权限进程发消息的失败的问题 if(windows::version::instance()->IsVistaOrLater()) { typedef BOOL (WINAPI ...
- Kendo Web UI Grid里时间格式转换
我和大家分享一下我的kendo的学习心得.如果不好的地方多多包含或者给我留言请看图 kendo里时间格式如图Oder Date列里的格式.但是我们想把时间转换成中国人习惯的格式.如Shipped Da ...
- LESSCSS
LESSCSS应需求而生 CSS 的语法相对简单,对使用者的要求较低,但同时也带来一些问题:CSS 需要书写大量看似没有逻辑的代码,不方便维护及扩展,不利于复用,尤其对于非前端开发工程师来讲,往往会因 ...
- 移动web前端的一些硬技能(一)关于移动设备
移动端web其实也就是小尺寸触摸屏设备的web,其中包含了两个词——1.小尺寸设备:2.触摸屏.能把这两个问题给解决掉,就已经是一个基本合格的移动web前端开发工程师了.对于前端工程师来说,前路漫漫, ...
- Nlog从下载到使用例子
第一.首先下载nlog.dll 下载地址:http://pan.baidu.com/s/1i3DQsV7 第二.添加nlog.ll的引用 第三.代码 private static Logger log ...
- 联通3g彩信设置
手机ME865,安卓2.3.6 添加接入点名称:3gwapAPN:3gwap代理:10.0.0.172端口:80服务器:http://www.wo.com.cnMMSC:http://mmsc.myu ...
- ASP.NET MVC3快速入门——第五节、从控制器访问模型中的数据
5.1 从控制器访问模型中的数据在本节中,我们将创建一个新的MoviesController类,并且书写代码来获取数据库中的数据,并通过视图模板来显示在浏览器中.鼠标右击Controller文件夹,点 ...
- .net 中的DllImport
只有做成COM的C++ dll才能直接引用.没有做成COM的就只能用P/Invoke(DllImport)或者C++/CLI那种.不过P/Invoke容易类型对不上,所以要是函数多,最好用C++/CL ...