C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面。ios
C语言中变量的定义必须在做用域开始的位置进行定义。c++
#include <stdio.h> int main(int argc, char *argv[]) { int i;//定义变量 int j; //使用变量 for(i = 0; i < 10; i++) { for(j = 0; j < 10; j++) { } } //error: 'for' loop initial declarations are only allowed in C99 mode for(int k = 0; k < 10; k++) { } return 0; }
C++更强调语言的实用性,C++中全部的变量能够在须要使用时再定义。express
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 7; //使用时定义变量 for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { } } return 0; }
在C语言中:编程
const int a = 100; int *p = &a;
在C++语言中:数组
const int a = 100;//必须在定义的时候初始化 const int *p = &a;//类型必须严格匹配
在C++语言中不能隐式转换数据类型。error: invalid conversion from 'const int*' to 'int*' [-fpermissive]
ide
C语言中,没有定义bool类型,表示真用非0,假用0。
C++语言中,定义了本身的bool类型,真为true,假为false,是基本类型。sizeof(bool) = 1
C++编译器会将非0值转换为true,0转换为false函数
#include <iostream> using namespace std; int main(int argc, char *argv[]) { bool a = 0; printf("a = %d\n", a);//0 bool b = -1; printf("b = %d\n", b);//1 b = b + 1;//1 + 1 = 2 => 1 printf("b = %d\n", b);//1 printf("sizeof(bool) = %d\n", sizeof(bool));//1 return 0; }
C语言中枚举本质就是整型,枚举变量能够用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化。
在C语言中,枚举的使用工具
#include <stdio.h> enum weekday { monday,tuesday,wednesday,thursday,friday,saturday,sunday }; int main(int argc, char **argv) { enum weekday day = monday; enum weekday a = sunday; enum weekday b = 100; //weekday c = sunday;错误用法,须要使用enum声明 printf("%d %d %d\n", day, a, b); return 0; }
在C++语言中枚举的使用oop
enum weekday { monday,tuesday,wednesday,thursday,friday,saturday,sunday }; int main() { weekday day = sunday; enum weekday a = monday; cout<<day<<" "<<a<<endl; return 0; }
C语言中表达式不能够作左值,C++中某些表达式能够作左值。(a = b) = 6;
学习
register关键字请求编译器将局部变量存储到寄存器中。
C语言中,没法获取register关键字修饰的局部变量的地址。
C++语言中,C++依然支持register关键字,但C++编译器对register关键字进行了优化,C++语言中能够获取register变量的地址。对于早期的C++编译器,C++编译器发现程序中须要获取register关键字修饰的变量地址时,register关键字对变量的声明失效;对于现代C++编译器,register关键字的存在只是为了兼容C语言,register关键字自己已经无任何意义。
C语言中能够重复定义多个重名的全局变量,同名的全局变量被连接到全局数据区的一个地址空间。
#include <stdio.h> //定义重名的全局变量合法 int a; int a; int main(int argc, char *argv[]) { return 0; }
C++语言不容许定义多个同名的全局变量。
#include <iostream> using namespace std; //定义重名的全局变量是非法的 int a; int a; //error: redefinition of 'int a' int main(int argc, char *argv[]) { return 0; }
C语言中,struct定义了一种变量的集合,struct定义的标识符不是一种新类型。C语言中的struct内部不能够定义函数。
#include <stdio.h> struct tag_student { const char* name; int age; }; typedef struct tag_student Student; int main(int argc, char *argv[]) { //合法定义 Student s; s.name = "lee"; s.age = 30; //非法定义 tag_student ts;//error: unknown type name 'tag_student' //合法定义 struct tag_student sts; sts.name = "bauer"; sts.age = 23; return 0; }
C语言中只有使用typedef关键字重命名struct后才可使用Student定义变量。
C++语言中struct用于定义一种全新的类型,可使用struct定义的标识符直接定义变量。
#include <iostream> using namespace std; struct Student { const char* name; int age; }; int main(int argc, char *argv[]) { Student s; s.name = "bauer"; s.age = 20; return 0; }
在C语言中,函数在定义时没有给出参数、返回值的类型,默认为int。
int f()表示返回值为int,接受任意参数的函数
f(void)表示返回值为int的无参数函数
#include <stdio.h> //接受任意个参数,返回int类型 func1() { return 30; } //无参函数,返回int类型 func2(void) { return 20; } //参数i默认为int类型 void func3(i) { printf("i = %d\n", i); } int main(int argc, char *argv[]) { int a = func1(1,2,3); printf("a = %d\n", a);//30 int b = func2(); printf("b = %d\n", b);//20 func3(10);//10 return 0; }
C++语言中,全部的标识符都必须显示的声明类型。C语言中的默认类型在C++中是不合法的。
#include <iostream> using namespace std; //error: ISO C++ forbids declaration of 'func1' with no type [-fpermissive] func1() { return 30; } //无参数,返回int int func2() { return 20; } int func3(void) { return 10; } int main(int argc, char *argv[]) { //error: too many arguments to function 'int func2()' int a = func2(20); int b = func2(); int c = func3(); return 0; }
C语言中,const修饰的变量是只读的,本质仍是变量,能够借助指针修改变量空间的值;const修饰的变量会分配存储空间,const修饰的局部变量分配在栈上,const修饰的全局变量分配在只读存储区,修改const修饰的全局变量的值将会致使异常错误;const只在编译期有效,在运行期无效;const关键词用于向编译器代表const修饰的变量不能作左值。
#include <stdio.h> //const全局变量,分配在只读存储区 const int number = 10; int main(int argc, char *argv[]) { const int c = 0; int* p = (int*)&c;//编译器会为c分配空间 printf("Begin...\n"); *p = 5; printf("c = %d\n", c);//5 c = 10;//error: assignment of read-only variable 'c' int* cp = &number; *cp = 100;//程序异常 const int number = 10; int array[number] = {0};//只读变量,编译时没法肯定其值 //error: variable-sized object may not be initialized printf("End...\n"); return 0; }
C++语言中,对C语言基础的const进行了优化处理。编译器编译过程当中遇到const修饰的标识符时,会将const修饰的标识符放入符号表中。若是后续编译过程当中发现const修饰的标识符时,直接使用符号表中const修饰的标识符对应的值直接替换。但在如下状况下C++编译器会给const声明的常量分配空间:
A、const修饰的常量为全局(extern修饰),而且须要在其它文件中使用
B、使用&操做符对cosnt常量取地址
C++编译器虽然会对const常量分配空间,但不会使用其存储空间的值。
const常量的判别:
A、只有用字面量初始化的const常量才会进入符号表
B、使用其余变量初始化的const常量仍然是只读变量
C、被volatile修饰的const常量不会进入符号表
const引用类型与初始化变量的类型相同时,初始化变量为只读变量;不一样时,生成一个新的只读变量。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { const int a = 10; //error: invalid conversion from 'const int*' to 'int*' int* p = &a; int* cp = (int*)&a; *cp = 100; printf("a = %d\n", a);//10 printf("*cp = %d\n", *cp);//100 int b = 3; //使用其它变量初始化的const常量是只读变量 const int c = b; //error: variable-sized object 'array' may not be initialized int array[c] = {0}; //使用volatile修饰的const常量不会进入符号表 volatile const int d = 10; //error: variable-sized object 'varray' may not be initialized int varray[d] = {0}; return 0; }
C++语言中const与宏定义的不一样在于,const常量由编译器处理,编译器会对const常量进行类型检查和做用域检查,而宏定义由预处理器进行处理,是单纯的文本替换。
#include <iostream> using namespace std; void func1() { #define NUMBER 100 const int number = 10; printf("NUMBER = %d\n",NUMBER); printf("number = %d\n",number); } void func2() { //宏定义没有做用域概念,预处理时直接替换 printf("NUMBER = %d\n",NUMBER); printf("number = %d\n",number); //'number' was not declared in this scope } int main(int argc, char *argv[]) { func1(); func2(); const int number = 10; //编译时使用符号表的值替换 int array[number] = {0}; return 0; }
C语言中,三目运算符返回变量的值,三目运算符表达式不能作左值使用。
C++语言中,三目运算符可直接返回变量自己,三目运算符表达式能够做为左值使用。可是当三目运算符表达式可能返回的值中有一个是常量值,则三目运算符表达式不能做为左值使用。
C++中,当三目运算符表达式可能返回的都是变量时,返回的是变量的引用;当三目运算符表达式可能返回的有常量值时,返回的是值。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 3; int b = 2; //返回变量自己,能够作左值 (a>b?a:b) = 10; printf("a>b?a:b = %d\n",a>b?a:b); //返回变量的值,不能作左值 (a<b?1:b) = 20; //error: lvalue required as left operand of assignment return 0; }
cin和cout是C++的标准输入流和输出流,在头文件 iostream 中定义。
流名 含义 隐含设备 流名 含义 隐含设备
cin 标准输入 键盘 cerr 标准错误输出 屏幕
cout 标准输出 屏幕 clog cerr 的缓冲输出 屏幕
int main() { char name[30]; int age; cout<<"pls input name and age:"<<endl; cin>>name; cin>>age; cout<<"your name is: "<<name<<endl; cout<<"your age is: "<<age<<endl; return 0; }
A、按进制输出数据类型
cout<<dec<<i<<endl; cout<<hex<<i<<endl; cout<<oct<<i<<endl;
B、设置域宽,设置左右对齐及填充字符
int main() { cout<<setw(10)<<1234<<endl; cout<<setw(10)<<setfill('0')<<1234<<endl; cout<<setw(10)<<setfill('0')<<setiosflags(ios::left)<<1234<<endl; cout<<setw(10)<<setfill('-')<<setiosflags(ios::right)<<1234<<endl; return 0; }
C、实型数据的设置
cout<<setw(5)<<'a'<<endl<<setw(5)<<100<<endl <<setprecision(2)<<setiosflags(ios::fixed)<<120.00<<endl;
C语言中不容许重名函数的存在。
C++语言中为了简化编程容许重名函数的存在,即便用同一个函数名定义不一样的函数,重名函数称为函数重载。
重载函数本质是定义的相互独立的不一样函数。当函数名和不一样的参数搭配时函数的含义不一样。
int abs(int a) { return a>0? a:-a; } double abs(double a) { return a>0? a:-a; }
函数重载的规则以下:
A、函数名相同。
B、参数个数不一样,参数的类型不一样,参数顺序不一样,都可构成重载。
C、返回值类型不一样则不能够构成重载。
#include <iostream> using namespace std; int func(int a, int b, int c = 0) { return a + b + c; } int func(int a, int b) { return a + b; } int main(int argc, char *argv[]) { //函数调用时出现二义性 int x = func(1,2); //error: call of overloaded 'func(int, int)' is ambiguous return 0; }
编译器调用重载函数的匹配规则以下:
A、将全部同名函数做为候选者
B、寻找可行的候选参数
C、匹配成功或失败
函数重载的匹配规则以下:
A、精确匹配实参,找到则调用。
B、经过默认参数可以匹配实参
C、经过默认类型转换匹配实参
经过默认类型转换匹配实参时,经过隐式转换寻求一个匹配,找到则调用。
C++容许int到long和double的隐式类型转换,所以在函数重载时会引发二义性,解决方法是在调用时强转类型。
#include <iostream> using namespace std; int func(int a, int b) { return a + b; } double func(double a, double b,double c) { cout << "func(double a, double b,double c)"<<endl; return a + b + c; } long func(long a, long b,long c) { cout << "func(long a, long b,long c)"<<endl; return a + b + c; } int main(int argc, char *argv[]) { int a = 1; int b = 2; int c = 3; //int xa = func(a,b,c);//出现二义性 int x = func((long)a,(long)b,(long)c); printf("x = %d\n", x); return 0; }
编译器调用重载函数匹配失败的规则:
A、若是最终找到的候选函数不惟一,则出现二义性,编译报错。
B、若是没法匹配全部候选者,函数未定义,编译报错。
重载函数使用默认参数可能会形成二义性。
#include <iostream> using namespace std; int func(int a, int b, int c = 0) { return a + b + c; } int func(int a, int b) { return a + b; } int main(int argc, char *argv[]) { //函数调用时出现二义性 int x = func(1,2); //error: call of overloaded 'func(int, int)' is ambiguous return 0; }
C++利用name mangling(倾轧)技术,来更名函数名,区分参数不一样的同名函数。
C++的name mangling实现使用 v c i f l d表示void char int float long double及其引用。
void func(char a); // func_c(char a) void func(char a, int b, double c);//func_cid(char a, int b, double c);
name mangling发生在两个阶段,.cpp编译阶段和.h的声明阶段。只有两个阶段同时进行,才能匹配调用。
#include <iostream> using namespace std; //函数类型:int(int,int) int func(int a, int b) { return a + b; } //函数类型:double(double, double, double) double func(double a, double b,double c) { cout << "func(double a, double b,double c)"<<endl; return a + b + c; } //函数类型:long(long,long,long) long func(long a, long b,long c) { cout << "func(long a, long b,long c)"<<endl; return a + b + c; } int main(int argc, char *argv[]) { int a = 1; int b = 2; int c = 3; //int func(int a, int b) printf("x1 = 0x%X\n", (int(*)(int,int))func); //long func(long a, long b,long c) printf("x2 = 0x%X\n", (long(*)(long,long,long))func); //double func(double a, double b,double c) printf("x3 = 0x%X\n", (double(*)(double,double,double))func); return 0; }
使用nm工具查看main.o文件中符号表信息的命令以下:nm.exe -a main.o
func重载函数的符号表信息以下:
00000036 T __Z4funcddd 00000029 T __Z4funcii 00000090 T __Z4funclll
上述的信息表示代码中的三个重载函数。
将重载函数名赋值给函数指针时,根据重载规则选择与函数指针参数列表一致的函数。重载函数的函数类型与函数指针类型必须严格匹配(不能有任何类型的隐式转换),此时函数返回类型将参与函数类型匹配。
函数重载必须发生在同一个做用域,没法经过函数名获得重载函数的入口地址。
重载函数的函数类型不一样。
#include <iostream> using namespace std; //函数类型:int(int,int) typedef int(*pFunc1)(int,int); int func(int a, int b) { cout << "func(int a, int b)"<<endl; return a + b; } //函数类型:double(double, double, double) typedef double(*pFunc2)(double,double,double); double func(double a, double b,double c) { cout << "func(double a, double b,double c)"<<endl; return a + b + c; } //函数类型:long(long,long,long) typedef long(*pFunc3)(long,long,long); long func(long a, long b,long c) { cout << "func(long a, long b,long c)"<<endl; return a + b + c; } int main(int argc, char *argv[]) { int a = 1; int b = 2; int c = 3; pFunc1 func1 = func; int x1 = func1(1,2);//int func(int a, int b) printf("func1 = %d\n", x1); pFunc2 func2 = func; int x2 = func2(1,2,3);//double func(double a, double b,double c) printf("func2 = %d\n", x2); pFunc3 func3 = func; int x3 = func3(1,2,3);//long func(long a, long b,long c) printf("func3 = %d\n", x3); return 0; }
重载函数的调用可能会存在隐式类型转换,好比int到long、double类型的转换,可是要函数指针调用重载函数时,函数指针的类型必须与重载函数的类型严格匹配。
函数重载的注意事项以下:
A、函数重载必然发生在同一个做用域中。
B、编译器须要使用参数列表或函数类型进行函数的选择。
C、不能直接经过函数名获得重载函数的入口地址。
C++彻底兼容C语言,所以必须彻底兼容C的类库。因为.c文件的类库文件中函数名并无发生name manling行为,而在包含.c文件所对应的.h文件时,.h 文件要发生name manling行为,于是会在编译连接时候发生错误。
C++为了不上述错误的发生,重载了关键字extern。只须要要避免name manling的函数前,加extern "C"若有多个,则extern "C"{}。
C语言标准库中实际上对C++语言程序引用时作了特殊处理,在C++语言编译器编译时使用extern "C"将C语言的标准库函数排除了命名倾轧。
为了确保不管在C、C++编译器中C代码以C语言方式编译:
#ifdef __cplusplus extern "C"{ #endif //c-style code #ifdef __cplusplus } #endif
C++编译器不能以C语言方式编译重载函数,C++编译器将函数名和参数列表编译为目标名,C语言编译方式只将函数名做为目标名进行编译。
C++调用C语言编码的.dll时,当包含.dll的头文件或声明接口函数时须要加extern “C”。
add.h源码:
#ifndef ADD_H #define ADD_H extern int add(int a, int b); #endif
add.c源码:
#include "add.h" int add(int a, int b) { return a + b; }
main.cpp源码:
#include <stdio.h> extern "C" { #include "add.h" } int main() { int c = add(10, 100); printf("%d\n", c); return 0; }
gcc add.c -o add.o g++ add.o main.cpp
C代码中引用C++的函数和变量时,C++头文件须要添加extern “C”,但在C代码中不能直接引用声明了extern “C”的C++头文件,C代码中只须要将C++中定义的extern “C”函数声明为extern类型便可。
add.h源码:
#ifndef ADD_H #define ADD_H extern "C" int add(int a, int b); #endif
add.cpp源码:
#include "add.h" int add(int a, int b) { return a + b; }
main.c源码:
#include <stdio.h> //#include "add.h" 错误 extern int add(int a, int b); int main() { int x = add(1, 2); printf("x = %d\n", x); return 0; }
编译:
g++ -c add.cpp -o add.o gcc main.c add.o -lstdc++
或是gcc add.cpp main.c -lstdc++
g++会自动进行C++标准库的链接;用gcc链接C++程序也能够,但须要人为指定链接C++标准库(-lstdc++),不然就会出现undefined reference to __gxx_personality_v/0
之类的错误。
C++提供了运算符重载机制。能够为自定义数据类型重载运算符。实现构造数据类型也能够像基本数据类型同样的运算特性。
struct COMP { float real; float image; }; COMP operator+(COMP one, COMP another) { one.real += another.real; one.image += another.image; return one; } int main() { COMP c1 = {1,2}; COMP c2 = {3,4}; COMP sum = operator+(c1,c2); //c1+c2; cout<<sum.real<<" "<<sum.image<<endl; return 0; }
实例代码重载了一个全局的操做符+号用于实现将两个自定义结构体类型相加。本质是函数的调用。
C++语言中,能够在函数声明时为参数提供一个默认值。当函数调用没有提供参数的值时,使用默认值。
函数默认参数的规则以下:
A、默认参数的顺序,是从右向左,不能跳跃。
B、定义在前,调用在后(此时定义和声明为一体),默认参数在定义处;声明在前,调用在后,默认参数在声明处。
C、一个函数,不能既做重载,又做默认参数的函数。当你少写一个参数时,系统没法确认是重载仍是默认参数。
函数调用时参数从左到右匹配,若是一个参数使用了默认值,则后续参数必须使用默认值。
A、单个参数
#include <iostream> #include <time.h> using namespace std; void weatherForcast(char * w="sunny") { time_t t = time(0); char tmp[64]; strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A ",localtime(&t) ); cout<<tmp<< "today is weahter "<<w<<endl; } int main(int argc, char *argv[]) { //sunny windy cloudy foggy rainy weatherForcast(); weatherForcast("rainny"); weatherForcast(); return 0; }
B、多个参数
#include <iostream> using namespace std; float volume(float length, float weight = 4,float high = 5) { return length*weight*high; } int main(int argc, char *argv[]) { float v = volume(10); float v1 = volume(10,20); float v2 = volume(10,20,30); cout<<v<<endl; cout<<v1<<endl; cout<<v2<<endl; return 0; }
C++语言中能够为函数提供占位参数,占位参数只有类型声明,没有参数名声明。因为C++类型检查较为严格,为兼容C语言,能够将函数参数默认值和占位参数结合使用。
C语言中func函数以下:
#include <stdio.h> void func() { } int main(int argc, char *argv[]) { func(5,10); return 0; }
C语言中func函数接收任意个数的参数。
C++语言中对func函数增长占位参数可使C语言中的func函数快速地知足C++语言的语法要求,代码以下:
#include <iostream> using namespace std; void func(int x, int = 0) { } int main(int argc, char *argv[]) { func(5,10); return 0; }
变量名,自己是一段内存的引用,即别名(alias)。引用,是为己有变量起一个别名。Type& name = var;
int a; int &b = a;
普通引用在定义时必须使用同类型的变量进行初始化。
A、引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不分配内存,与被引用的变量有相同的地址。
B、声明的时候必须初始化,一经声明,不可变动。
C、可对引用再次引用。屡次引用的结果,是某一变量具备多个别名。
D、&符号前有数据类型时,是引用。其它皆为取地址。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 3; int c = 6; float f = 3.14; int& b = a; printf("&a = 0x%X\n", &a); printf("&b = 0x%X\n", &b); //error: redeclaration of 'int& b' int& b = c;//error //给b赋值 b = c; //引用的类型必须与变量类型相同 int& d = f;//error //error: invalid initialization of reference of type 'int&' from expression of type 'float' //引用在定义时必须初始化 int& rd;//error //error: 'rd' declared as reference but not initialized //引用不可使用字面值初始化 int& r = 10;//error //error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int' return 0; }
函数中的引用形参不须要进行初始化,函数调用时进行初始化。
#include <iostream> using namespace std; //引用 void swap(int &a, int &b) { int tmp; tmp = a; a = b; b = tmp; printf("swap(int &a, int &b)\n"); } //指针 void swap(int* a, int* b) { int tmp; tmp = *a; *a = *b; *b = tmp; printf("swap(int* a, int* b)\n"); } int main(int argc, char *argv[]) { int a = 3; int b = 6; swap(a,b); printf("a = %d\n",a);//6 printf("b = %d\n",b);//3 swap(&a,&b); printf("a = %d\n",a);//3 printf("b = %d\n",b);//6 return 0; }
四、引用的提升
A、能够定义指针的引用,但不能定义引用的引用。
int a; int* p = &a; int*& rp = p; // ok int& r = a; int&& rr = r; // error
B、能够定义指针的指针(二级指针),但不能定义引用的指针。
int a; int* p = &a; int** pp = &p; // ok int& r = a; int&* pr = &r; // error
C、能够定义指针数组,但不能定义引用数组,能够定义数组引用。
int a, b, c; int* parr[] = {&a, &b, &c}; // ok int& rarr[] = {a, b, c}; // error int arr[] = {1, 2, 3}; int (&rarr)[3] = arr; // ok 的
数组是连续的存储空间,数组中的元素若是是引用,会致使数组的元素存储不连续。引用数组会破坏数组存储空间的连续性。
const引用所引用的对象必须是const的,将普通引用绑定到const引用对象是不合法的。 const type& name = var;
const引用可以使用相关类型的对象(常量,非同类型的变量或表达式)初始化,const引用让变量具备只读属性,是const引用与普通引用最大的区别。
非const引用只能绑定到与该引用同类型的对象。
当const引用使用字面常量值初始化时,C++编译器会为常量值分配空间,使用字面常量对const引用初始化将生成一个只读变量。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 10; const int& ra = a;//const引用,为只读变量 //只读变量不能做为左值 ra = 100;//error: assignment of read-only reference 'c' int* p = (int*)&ra; *p = 5; printf("ra = %d\n", ra);//5 const int& rb = 10;//rb为只读变量,占用内存空间 //只读变量不能做为左值 rb = 100;//error int arraya[ra] = {0};//error //error: variable-sized object 'arraya' may not be initialized int arrayb[rb] = {0};//error //error: variable-sized object 'arrayb' may not be initialized double pi = 3.14; int& rpi = pi;//非法 //error: invalid initialization of reference of type 'int&' from expression of type 'double' const int& crpi = pi;//合法 printf("rpi= %d\n",crpi);//3 return 0; }
C++编译器在编译过程当中使用指针常量做为引用的内部实现,所以引用所占用空间大小与指针相同。引用的本质是一个指针常量。type & name<====> type * const name;
使用引用时不能返回局部变量的引用
#include <iostream> using namespace std; int main(int argc, char *argv[]) { printf("sizeof(char&) = %d\n", sizeof(char&));//1 char c = 'a'; char& rc = c; printf("sizeof(char&) = %d\n", sizeof(rc));//1 return 0; }
上述代码的汇编代码以下:
将字符’a’(0x61)存储到指针寄存器0x1b,将0x1b放入eax数据寄存器中,再将eax数据寄存器存储内容放入指针寄存器0x1c中。
C语言中提供了malloc和free两个系统函数,完成对堆内存的申请和释放。而C++则提供了两关键字new和delete。
new分配内存空间时,不能保证按需分配,分配内存空间大小可能会大于所需空间大小。所以,new会分配至少申请大小的内存空间。
A、开辟单变量地址空间
int *p = new int; //开辟大小至少为sizeof(int)空间 int *a = new int(5); //开辟大小至少为sizeof(int)空间,并初始化为 5
B、开辟数组空间
一维: int a = new int[100];//开辟一个大小很多于400字节的整型数组空间
二维: int (a)[6] = new int[5][6]
三维: int (*a)[5][6] = new int[3][5][6]
A、释放单变量空间
int *a = new int; delete a; //释放单个 int 的空间
B、释放数组空间
int *a = new int[5]; delete []a; //释放 int 数组空间
int *p = new int(5); cout<<*p<<endl; delete p; char *pp = new char[10]; strcpy(pp,"china"); cout<<pp<<endl; delete []pp; string *ps = new string("china"); cout<<*ps<<endl; //cout<<ps<<endl; delete ps; char **pa= new char*[5]; memset(pa,0,sizeof(char*[5])); pa[0] = "china"; pa[1] = "america"; char **pt = pa; while(*pt) { cout<<*pt++<<endl; } delete []pt; int (*qq)[3][4] = new int[2][3][4]; delete []qq;
//C++ 内存申请失败会抛出异常 try{ int *p = new int[10]; } catch(const std::bad_alloc e) { return -1; } //C++ 内存申请失败不抛出异常版本 int *q = new (std::nothrow)int[10]; if(q == NULL) return -1;
C++中堆空间的分配和释放注意事项以下:
A、new/delete 是关键字,效率高于 malloc 和 free.
B、配对使用,避免内存泄漏和多重释放。
C、避免交叉使用。好比 malloc 申请的空间去 delete,new 出的空间被 free;
D、重点用在类对像的申请与释放。申请的时候会调用构造器完成初始化,
释放的时候,会调用析构器完成内存的清理。
malloc与new的区别以下:
A、new是C++关键字,malloc是C语言库函数
B、new以具体类型为单位进行内存分配,malloc以字节位单位分配内存
C、new在申请单个类型变量时能够进行初始化,malloc不具有
D、new在全部C++编译器中都支持,malloc在某些系统开发中不可调用
E、new可以触发构造函数的调用,malloc仅分配须要的内存空间
F、对象的建立只能使用new,malloc不适合面向对象开发
free与delete的区别以下:
A、delete是C++关键字,free是库函数
B、delete在全部C++编译器中都支持,free在某些系统开发中不可调用
C、delete可以触发析构函数的调用,free仅归还分配的内存空间
D、对象的销毁只能使用delete,free不适合面向对象开发
E、free能够归还new申请的内存空间,但不会调用析构函数,可能会形成内存泄漏
F、delete能够释放malloc分配的内存空间,但会调用析构函数,会形成其余问题。
C语言中有宏函数的概念。宏函数的特色是内嵌到调用代码中去,避免了函数调用的开销。但宏函数的处理发生在预处理阶段,缺乏做用域检查和类型检查。
C++提供了inline关键字,请求C++编译器将一个函数进行内联编译(C++编译器能够拒绝),C++编译器会直接将内联的函数体代码插入函数调用的地方。
内联函数声明时inline关键字必须和函数定义结合在一块儿,不然编译器会直接忽略内联请求。
inline int sqr(int x) { return x*x; }
内联函数的优势:避免调用时的额外开销(入栈与出栈操做)
内联函数的缺点:内联函数的函数体在代码段中会出现多个“副本”,所以会增长代码段的空间。
内联函数的本质:以牺牲代码段空间为代价,提升程序的运行时间的效率。
内联函数的适用场景:函数体很“小”,且被“频繁”调用。
Inline关键字是对编译器的建议,若是编译器认为inline声明的函数能够内联,则编译器会将函数内联,若是编译器认为inline声明的函数的函数体太长,则不会内联,按普通函数处理。
using namespace std; inline int func(int a, int b) { return a*a + b*b; } int main(int argc, char *argv[]) { int a = 3; int b = 4; int c = func(a,b); return 0; }
上述代码在QtCreator+MinGW编译器下调试时,查看汇编代码以下:
调用func函数时,C++编译器没有将func函数内联,仍然使用函数调用。
inline声明的函数,内联请求可能被C++编译器拒绝。
现代C++编译器可以进行编译优化,一些函数即便没有inline关键字声明也可以内联编译,同时现代C++编译器提供了扩展的语法,可以对函数进行强制内联。如现代G++编译器使用__attribute__((always_inline))
声明强制内联,MSVC编译器使用__forceinline
声明强制内联,再也不使用inline关键字。
#include <iostream> using namespace std; __attribute__((always_inline)) int func(int a, int b); int main(int argc, char *argv[]) { int a = 3; int b = 4; int c = func(a,b); return 0; } int func(int a, int b) { return a*a + b*b; }
上述代码在QtCreator+MinGW编译器中进行调试时,main函数中func函数调用代码的汇编代码以下:
MinGW编译器已经对func函数进行了内联。
C++中使用inline关键字内联编译函数的限制:
A、不能存在任何形式的循环语句
B、不能存在过多的条件判断语句
C、函数体不能过于庞大
D、不能对函数进行取地址操做
E、函数内联声明必须在调用语句前
对于现代C++编译器的扩展语法提供的强制内联不受上述条件限制。
C语言中的类型转换是强制转换,任何类型间均可以转换,过于粗暴。
C++语言引入了static_cast、dynamic_cast、const_cast、reinterpret_cast四个关键字处理不一样类型间的转换。
静态类型转换是在编译期内便可决定其类型的转换。
静态类型转换的使用场合:
A、用于基本类型间的转换
B、不能用于基本类型指针间的转换
C、用于有继承关系类对象间的转换和类指针间的转换(转换通常从子对象向父对象转换)
语法格式:
static_cast<目标类型> (标识符)
应用实例:
#include <stdio.h> class A { private: int a; int b; public: A() { a = 0; b = 0; } void print() { printf("a = %d, b = %d\n", a, b); } }; class B : public A { private: int c; int d; public: void display() { printf("c = %d, d = %d\n", c, d); } }; class C { public: void print() { printf("hello\n"); } }; int main() { float f = static_cast<float>(9)/10;//基本类型的转换 printf("f = %f\n", f); A a; B b; A aa = static_cast<A>(b);//将子类对象转换为父类对象 aa.print(); //B bb = static_cast<B>(a);//不能将父对象转换为子对象 A* pa = static_cast<A*>(&a);//在同类型对象指针间转换 pa->print(); pa = static_cast<A*>(&b);//将子类对象指针转换为父类对象指针 pa->print(); B* pb = static_cast<B*>(&b); pb->display(); pb = static_cast<B*>(&a); pb->display(); //C c = static_cast<C>(a);//没有转换构函数,不能将A类型转换为C类型 //c.print(); return 0; }
动态类型转换的使用场合:
A、用于有继承关系的类指针间的转换
B、有交叉关系的类指针间的转换
C、具备类型检查
D、必须有虚函数支持
语法格式:
dynamic_cast<目标类型> (标识符)
用于有直接或间接继承关系的指针(引用)的强制转换
转换指针成功将会获得目标类型的指针,转换失败将获得一个空指针;
转换引用成功将获得目标类型的引用,转换失败将获得一个异常操做信息。
使用实例:
#include <iostream> #include <string> using namespace std; class Base { public: Base() { cout << "Base::Base()" << endl; } virtual ~Base() { cout << "Base::~Base()" << endl; } }; class Derived : public Base { }; int main() { Base* p = new Derived; Derived* pd = dynamic_cast<Derived*>(p);//将指向子类对象的父类指针转换为子类指针,转换成功 if( pd != NULL ) { cout << "pd = " << pd << endl; } else { cout << "Cast error!" << endl; } delete p; cout << endl; p = new Base; pd = dynamic_cast<Derived*>(p);//将指向父类对象的父类指针转换为子类指针,转换失败 if( pd != NULL ) { cout << "pd = " << pd << endl; } else { cout << "Cast error!" << endl; } delete p; return 0; }
常量类型转换的使用场合:
A、用于去除变量的只读属性
B、目标类类型只能是指针或引用
语法格式:
const_cast<目标类型> (标识符) //目标类类型只能是指针或引用。
const_cast将转换掉表达式的const属性
应用实例:
#include <iostream> using namespace std; int main() { const int& a = 10; int& b = const_cast<int&>(a);//将a的只读属性去除,并初始化b b = 0; cout << "a = " << a << endl;//0 cout << "b = " << b << endl;//0 const int c = 100; int& d = const_cast<int&>(c);//为c分配一个只读空间 //int x = const_cast<int>(c);//error,目标类型只能为指针和引用 d = 1000; cout << "c = " << c << endl;//100 cout << "d = " << d << endl;//1000 return 0; }
重解释类型转换使用场合:
A、用于指针类型间的强制转换
B、用于整数和指针类型间的强制转换
语法格式:
reinterpret_cast<目标类型> (标识符)
为数据的二进制形式从新解释,可是不改变其值。
使用实例:
#include <iostream> using namespace std; int main(int argc, char *argv[]) { int a = 100; char c = 'a'; int* pa = reinterpret_cast<int*>(&c); printf("*pa = %d\n", *pa);//687781729 printf("*pa = %c\n", *pa);//'a' //int x = reinterpret_cast<int>(c);//error //int y = reinterpret_cast<int>(1.1);//error return 0; }
C语言中,只有一个全局做用域,全部的全局标识符共享一个做用域,所以标识符之间可能存在冲突。
C++语言中,提出了命名空间的概念。命名空间将全局做用域分为不一样的部分,不一样命令空间中的标识符能够重名而不会发生冲突,命名空间能够嵌套。全局做用域即默认命名空间。
global scope是一个程序中最大的scope,是引发命名冲突的根源。C语言没有从语言层面提供命名空间机制来解决。global scope是无名的命名空间。
NameSpace是对全局区域的再次划分。
命名空间的声明以下:
namespace NAMESPACE { 全局变量 int a; 数据类型 struct Stu{}; 函数 void func(); }
直接指定命名空间: NameSpace::a = 5;
使用using+命名空间+空间元素:using NameSpace::a; a = 2000;
使用using +namespace+命名空间;
#include <iostream> using namespace std; namespace MySpace { int x = 1; int y = 2; } namespace Other { int x = 3; int y = 4; } int main() { { using namespace MySpace; cout<<x<<y<<endl; } { using namespace Other; cout<<x<<y<<endl; } { MySpace::x = 100; Other::y = 200; cout<<MySpace::x<<Other::y<<endl; } return 0; }
可使用块语句将命名空间限定在块语句内部。
namespace MySpace { int x = 1; int y = 2; namespace Other { int m = 3; int n = 4; } }
在实际项目开发中,能够将一个类或者具备相同属性的多个类声明在一个命名空间内,在使用时只须要声明命名空间便可。
#ifndef A_H #define A_H namespace XX { class A { public: A(); ~A(); }; } #endif // A_H
#include "a.h" using namespace XXX { A::A() {} A::~A() {} }
除了使用字符数组来处理字符串之外,C++引入了字符串类型。能够定义字符串变量。
string str; str = "china"; string str2 = " is great "; string str3 = str2;
string str = "china"; cout << sizeof(str) << " " << str.max_size() << " " << str.size()<<endl;
4 1073741820 5
A、赋值string str3 = str2;
B、加法string combine = str + str2;
C、关系
string s1 = "abcdeg"; string s2 = "12345"; if(s1>s2) cout<<"s1>s2"<<endl; else cout<<"s1<s2"<<endl;
string数组是高效的,若是用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。若是用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,可是操做麻烦。用 string 数组存储,字符串数组的话,效率即高又灵活。
string sArray[10] = { "0", "1", "22", "333", "4444", "55555", "666666", "7777777", "88888888", "999999999", }; for(int i=0; i<10; i++) { cout<<sArray[i]<<endl; }
int capacity()const; //返回当前容量(即string中没必要增长内存便可存放的元素个数) int max_size()const; //返回string对象中可存放的最大字符串的长度 int size()const; //返回当前字符串的大小 int length()const; //返回当前字符串的长度 bool empty()const; //当前字符串是否为空 void resize(int len,char c);//把字符串当前大小置为len,并用字符c填充不足的部分