这一篇主要关于我最近在看C++时的一些感想和一些语法细节。
C++进阶之路(图片来自知乎):
C++ Started
目前仍然算是开始阶段,C++ Primer Plus 是还没有看完的。
在写下了hello,world:
#include<iostream>
using namespace std;
int main(void)
{
cout << "Hello,world!" << endl;
return 0;
}
之后,又粗略了解的C++的class之后,我感觉自己掌握了世界。C++不过如此嘛,自信是有的。然而学习C++的 self-confidence 曲线早已预知了一切。(渺小的凡人!)
而我一直以来都是把C++当做 C with classes 来用的,不对,应该是 C with cin and cout,输入输出用cin和cout,简洁明了,再加上一个看起来无比牛B的iostream头文件,瞬间感觉自己也是懂C++的男人了。现在我只想对以前那个逗比说呵呵。
而知乎上一位答主给出的C++学习阶段的等级,虽然我对C++并没有清醒的认识,但这个应该还是有一定参考性的。后面的等级对其他编程语言应该也可以参考。我认为自己是可以在第0级前徘徊的。我还能说什么呢。会不会成为死在门前的小白鼠呢。
以上是对自己的吐槽以及学习方向的参考。
以下是一些区别于C的C++细小语法点(并不全面,当然不会把整本书抄上来)。
命名空间 namespace
C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
using namespace std;
也可以将要使用的函数或对象提前使用using声明,比如要使用cin和cout的话:
using std::cin;
using std::cout;
也可以在使用时加上cout前缀,std::cin
或者std::cout
。
基本数据类型
C++中基本数据类型和C一致。C++为了保持和C语言具有良好的兼容性,保持了很多C语言的特性(Zao Po)。
整型
长度上来说有short、int、long (int)、long long (int)四种。长度分别为16bit,16或32bit,32bit,64bit。
从符号上来说有 unsigned 和 signed(缺省),即无符号和有符号。
浮点型
float(32bit),double(64bit),long double(128)bit。同样有unsigned。
字符型
char(8bit),unsigned char,宽字符型wchar_t。C++11新增 char16_t(16bit) 和 char32_t(32bit)。
bool类型
true 和 false 两个取值。隐式转换:一切非0值转换为true,0转换为false。
const 常量
const限定符修饰,不可变,不可赋值。用来取代C语言中的宏定义(定义符号常量时)。宏定义#define的本质是替换,在某些不严谨的编码中会产生问题,const则不会。然而宏定义在某些情况下(比如在头文件中)依然很常用。
在C++中,应该用以下代码来替代宏定义。
const Max_Size = 100;
取代:
#define Max_Size 100;
运算符
同C语言,优先级等,不赘述。
初始化
C++中将使用{}来初始化叫做初始化列表(list-initialization)。初始化列表比直接赋值更加严格。
格式:
int a {1}; //a = 1
或者
int a = {1};
如果在使用初始化列表时将double类型赋给int型,在GCC编译器,ISOC++11标准下会报warning。
类型转换
在表达式中会将弟优先级的类型转换为高优先级的。大体上 long double > double > double > long long > long > int > short > char , signed > unsigned。(并不十分严谨,请查阅C++编译器校验表)
传递参数时形参实参类型不同也会转换。
强制类型转换
C语言写法:
(typename) value
C++标准写法:
typename (value)
C++还引入了强制类型转换运算符,使用要求更为严格,更安全。如static_cast<typename> (value)
auto声明
C++11的auto关键字可以让你不用定义数据类型。如:
auto a = 1; //int
auto b = 1.0; //duoble
auto x = 1.3e14L; //long double
但实际上auto并不是这样来使用的。
真正用法,例如C++98下的下列代码:
std::vector<double> scores;
std::vector<double>::iterator itor = scores.begin();
在C++11中完全可以写作:
std::vector<double> scores;
auto itor = scores.begin();
复合数据类型
更新于2017.12.4。时间过得真快啊。
数组
C中的数组
数组声明格式
typename arrayname[arraysize];
数组在内存中的存储是连续的,数组名被解释为指向数组第一个元素的指针,每个单元的大小就是typename
的字节。不同于一般的指针变量,数组名不可被赋值。&arrayname
表示指向整个数组的指针,单元大小是整个数组大小。(arrayname和&arrayname表示的地址相同但含义不同)。
int A[4] = {1,2,3,4};
cout << A << endl;
cout << &A << endl; //A和&A地址一致
cout << A+1 << endl; //A+1地址比A大4个字节(64位windows中int类型大小为4字节)
cout << &A+1 << endl; //&A+1地址比A大16个字节(&A指向整个数组)
若在定义时由初始化列表初始化,可不指定数组大小。此时数组大小确立由编译器完成。例如:
int a[] = {1,2,3,4}; //数组大小为4
C99标准中支持变长数组(VLA,variable length array),C89并不支持,GCC编译器同时支持了C和C++的语法,支持这一特性。而在VC++因为使用C++的编译器(对C语言只支持到了C89)可能不支持。也就是说下列代码在GCC编译器向下是可执行的。
int n;
int A[n];
cin >> n;
for(int i=0;i<n;i++)
{
cin >> A[i];
}
突然想到大一时C语言老师按照C89标准讲的知识,数组长度必须是常数,然后用VC++6.0写C。无力吐槽。
C语言中的数组并不提供越界检查,也就是说数组下标管理需要程序员来完成。数组下标越界属于未定义行为,在不同机器,不同编译器环境下可能出现不同结果。一般情况下可能出现程序能编译但运行时会崩溃。
在C/C++数组中,A[i]完全等价于*(A+i),所以说存在下列有趣的现象。这在C语言/C++中是完全正确的语法,一定程度上来说并不安全,所以C++提供了可以代替数组的更安全的类。
for(int i=0;i<n;i++)
{
cin >> i[A]; //等价于*(i+A),即*(A+i)
}
C++中数组的替代
C++提供了两个类来替代数组,array类(C++11)和vector类。本质上来说是对数组的封装,比C风格的数组更安全。
array对象长度不可变,需包含头文件array
,声明格式:
array<typename,arraysize> arr; //arraysize不能是变量
vector对象长度可变,并且在运行时可以改变长度,需包含头文件vector
,声明格式:
vector<typename> vec(n); //n可以是整型变量或常量
array类和vector类可以通过和数组相同的访问方式——即[ ]
运算符访问。由于vector类长度可变,所以array类性能高于vector类。
字符串
C风格字符串
C语言中用char数组表示字符串,字符串以空字符'\0'
结尾,不以'\0'
结尾的char数组不是字符串。对C风格字符串的操作结束标志都是'\0'
。与普通数组相同,字符串名称指向字符串的第一个字符地址(字符串首地址)。
初始化:
char str[] = "hello,world!";
则字符串长度为12,但字符数组大小为13,初始化时编译器会在末尾自动填’\0’。
C中库string.h
,C++中cstring
库(C中的标准库在C++中都加上了前缀c且不带后缀.h,如math.h
变为cmath
,string.h
变为cstring
)提供了一系列C风格字符串的操作。如求串长的strlen()
,比较两个字符串的strcmp()
,拷贝字符串的strcpy()
,拼接字符串的strcat()
等。
对于定义时初始化的字符串,字符串求串长的函数strlen()
求得的是字符串长度,而用sizeof()
方法求得的尺寸要包含末尾的'\0'
,既有sizeof(str) = strlen(str)+1。
在C++中,字符串常量同样是用字符串首地址来表示。比如可以通过如下方式来打印字符串第一个字符。
cout << *"hello"; //打印结果为h
cout << "world"[2]; //打印结果为l
C++中提供字符串按行输入方法cin.getline(str,len)
,则字符串最长长度为len-1。
C++中的string类
C++中提供的string类来更好的表示字符串,string对象无固定长度,可直接赋值,可使用[]
运算符访问字符,提供了一系列处理字符串的方法。例如length()
/size()
方法求串长,可直接用+
运算符拼接两个字符串,可直接使用==
判断字符串相等/或者使用compare()
方法,etc.
例如:
string str = "hello,world!";
string类同样有按行输入方法getline(cin,str)
。
这里简要说明,以后会有标准模板库的详细介绍。
指针与自由存储空间
指针(pointer)
指针变量的定义使用运算符*
,指针变量保存的内容是地址。定义格式如下。
typename *pointer; //则pointer是一个指向typename类型的指针
例如指向int类型的指针的意思是指针变量中存放着一个int变量的地址。通过取地址运算符&
可以将int类型变量的地址赋给指针,解除引用运算符*
(或者说取内容)可以取出指针变量的地址指向的变量。
int a = 1;
int *p = &a; //定义时初始化,指针变量p的值即为a的地址
//p = &a; //定以后初始化,与上面等价
cout << *p << endl; //*p等价于a,即会打印出1
cout << p << endl; //打印指针变量保存的地址,即是&a,格式为一个十六进制数
cout << &a << endl; //与p内容相同
对于上述指针变量p,我们称其类型为 int *
类型,即指向int的指针。但是在同时定义多个指针变量时,需要在每一个指针前加上*
。
int * b,c; //定义了一个指向int的指针b,和一个int型变量c
指针变量在定义时即被分配内存,但是计算机并不会分配指针所指向数据的内存,所以指针必须先初始化为一个变量的地址才可使用。所以以下代码是错误的,尽管编译器可能并不会报错,但很可能会崩溃。如果p指向内存中系统部分内存,则有可能造成系统崩溃。
int *p; //未初始化,初值随机
*p = 20; //不能这样操作,将20存放在了一个未知的地址,将造成错误
上述指针是指向基础类型的,指针还可以指向对象、结构、或者指针。
自动存储(栈中分配)
在函数内部(包括main函数)定义的变量称为局部变量。程序运行时局部变量在栈(stack)中分配,当程序离开变量所属代码块后便被释放(出栈)。局部变量的作用域为所属代码块,即用{ }
括起来的部分。
静态存储
全局变量或者用关键字static
修饰的静态变量存储方式为静态存储,其在整个程序执行期间都存在。其中全局变量(在函数外定义)在整个程序中都可以使用。静态变量如果在局部代码块中定义,则为静态局部变量,则在整个程序执行期间都存在,但只能在该代码块中使用。
动态存储(堆中分配)
在C++中,可以通过new
运算符在堆(heap,或称为自由存储空间)上分配内存。这种存储方式成为动态存储。对应的释放所分配的内存的运算符为delete
。
int *p = new int; //申请一个int大小的内存
int *pn = new int[Size]; //申请一块内存,大小为sizeof(int)*Size
delete p; //释放内存
delete [] pn; //释放内存块
p = NULL; //释放内存后为避免以后再使用到该指针时造成错误应将指针置为空(NULL)
pn = NULL;
申请了内存后便可使用指针访问,申请的内存块也叫作动态数组,可以通过数组的方式访问。
在C语言中使用malloc
和free
函数来申请和释放内存。格式如下:
int *p = (int *)malloc(sizeof(int)); //使用(int *)强制类型转换将所分配到的内存块地址转换为int *类型
int *pn = (int *)malloc(sizeof(int)*Size); //分配内存块大小作为参数传入malloc函数
free(p); //释放方式相同
free(pn);
结构体
使用struct
关键字定义,是OOP(面向对象)思想的基础【类是面向对象的基础,而结构体是类的基础】。结构(体)是对不同数据的封装。结构可以将任意类型任意数目的数据封装为一种数据类型。然后利用结构创建变量,通过这个变量即可调用它的数据成员。
例如:
struct people
{
string name;
int age;
double height;
bool is_male;
};
这里定义了一个people(人)的结构,包含人的四个属性:姓名、年龄、身高、性别。使用people结构创建了变量之后,便可以对其赋值,可以用.
运算符调用其数据成员,如果有一个指向结构的指针,可以通过->
算符来调用其指向的结构的数据成员。例如:
people Jim = {"Jim",18,178.5,true}; //可以使用初始化列表按顺序对其数据成员赋值
cout << "name: " << Jim.name << endl; //使用.运算符调用其数据成员
cout << "age: " << Jim.age << endl;
cout << "height: " << Jim.height << endl;
cout << "gender: " << (Jim.is_male?"boy":"girl")<< endl;
people *p_kim = new people; //动态分配
*p_kim = {"Kim",20,165,false};
cout << "name: " << p_kim->name << endl; //还用->运算符调用p_kim指向的结构的数据成员
cout << "age: " << p_kim->age << endl;
cout << "height: " << p_kim->height << endl;
cout << "gender: " << (p_kim->is_male?"boy":"girl")<< endl;
delete p_kim;
p_kim = NULL;
结构可以像基本数据类型那样相互赋值,或者作为函数的参数或者返回值。
people the_2nd_Jim = Jim; //赋值时对应所有数据项全部被拷贝
共用体
公用体是一种数据格式,能够存储不同数据类型,但同时只能一种类型。节省了内存。可用在C语言/C++编写嵌入式程序。使用union
关键字定义,定义格式类似于结构,这里不赘述。
枚举
C++中可使用enum
关键字创建符号常量,某些情况下这种方式可以替代const
。如果按如下语句:
enum color{red,green,blue,yellow,orange};
则color成为一种新数据类型的名称,即枚举。所以可以通过强类型转换将一个枚举中存在的整数转换为一个枚举量。默认情况下上面五个表示颜色的符号常量会被赋予相对应的整数值0~5,也可以被显式地赋予一个整数。从前往后,每个符号常量的值会依次增大1。
以上全部抄书(C++ Primer Plus)