(原) 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#异世界的小故事吧,更新随缘,也稍微能让自己轻松些. ...
随机推荐
- hdu EXCEL排序
Problem Description Excel可以对一组纪录按任意指定列排序.现请你编写程序实现类似功能. Input 测试输入包含若干测试用例.每个测试用例的第1行包含两个整数 N (<= ...
- 使用python发邮件
使用python发邮件 网上有很多发邮件的例子,本人在网上找了一份,稍加修改后使用 上源码 # encoding=utf-8 from email.mime.image import MIMEImag ...
- smarty模板引擎中section循环loop与total的区别
在smarty模板引擎的section循环中 $data=[101,102,103,105,104]; section的两个属性total与loop {section foo $data start= ...
- OSharp 学习(一)
OSharp3.0 由郭明峰组织开发,已经相对比较完整,从底层代码开始阅读难度比较大. 故采用自上而下的方式,使用到的时候再完善相关功能.希望通过此教程能够找到志同道合的人. 注:请勿抄袭 请参看OS ...
- mysql在关闭时的几个阶段
mysql关闭的大致过程 1.The shutdown process is initiated 初始化关机过程有许多种方法1.mysqladmin shutdown ; 2.kill pid_of_ ...
- [TYVJ] P1003 越野跑
越野跑 背景 Background 成成第一次模拟赛 第二道 描述 Description 为了能在下一次跑步比赛中有好的发挥,贝茜在一条山路上开始了她的训练.贝茜希望能在每次训练中跑 ...
- Qt下HBoxLayout里的按钮有重叠
没想到是一个bug,而且六年了都没有解决: https://bugreports.qt.io/browse/QTBUG-14591 http://stackoverflow.com/questions ...
- 《Programming WPF》翻译 第8章 2.Timeline
原文:<Programming WPF>翻译 第8章 2.Timeline Timeline代表了时间的延伸.它通常还描述了一个或多个在这段时间所发生的事情.例如,在前面章节描述的动画类型 ...
- PowerShell_零基础自学课程_9_高级主题:静态类和类的操作
上次我们说到了,wmi对象和com组件,今天我们继续来看PS中对象的相关内容.主要说一下静态对象和对象的基本操作. 一.静态对象 在PS中有一类特殊的对象,我们不能同过这些类创建新的对象,这些类是不能 ...
- 需要考虑的9个SEO实践
搜索引擎优化重要吗?我们知道,网站设计是把屏幕上平淡无奇变成令人愉快的美感,更直观地辨认信息.这也是人与人之间在沟通想法,这样的方式一直在演变. 穴居人拥有洞穴壁画,古埃及人有象形文字,现代人有网页设 ...