一 、C++编译器及使用
1.windows直接安装visual stdio2019
2.liunx或mac
1)文本编辑器
vim/vi
2)编译器
最常用的免费可用的编译器是 GNU 的 C/C++ 编译器, GNU 的 gcc 编译器适合于 C 和 C++ 编程语言。
liunx终端输入:g++ -v
。检查GCC是否安装
mac系统:安装Xcode开发环境,就能使用GNU编译器
3.使用g++
程序 g++ 是将 gcc 默认语言设为 C++ 的一个特殊的版本,链接时它自动使用 C++ 标准库而不用 C 标准库
三种使用:
1) g++ helloworld.cpp
//未指定可执行程序的名字 ,默认为a.out
./a.out
//执行程序
2)
g++ helloworld.cpp -o helloworld
//生成名字为helloworld的可执行文件
./helloworld
//执行
3)g++ runoob1.cpp runoob2.cpp -o runoob
编译多个源文件
二 、C++数据类型
七种基本数据类型
wchar_t
等于 typedef short int wchar_t;
char 1个字节
int 4个字节
short int 2个字节
long int 8个字节
float 4个字节
变量大小跟编译器和使用电脑有所不同
枚举类型:如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型
三 变量及函数声明
当您使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。您可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。
#include <iostream>
using namespace std;
// 变量声明
extern int a, b;
extern int c;
extern float f;
int main ()
{
// 变量定义
int a, b;
int c;
float f;
// 实际初始化
a = 10;
b = 20;
c = a + b;
cout << c << endl ;
f = 70.0/3.0;
cout << f << endl ;
return 0;
}
理解声明与定义的区别
定义包含了声明,但是声明不包含定义,
int a = 0; //定义并声明了变量 a
extern int a; //只是声明了有一个变量 a 存在,具体 a 在哪定义的,需要编译器编译的时候去找。
函数声明也是如此,提供一个函数名,而函数的实际定义则可以在任何地方进行
// 函数声明
int func();
int main()
{
// 函数调用
int i = func();
}
// 函数定义
int func()
{
return 0;
}
C/C++ 编译 cpp 文件是从上往下编译,所以 main 函数里面调用其他函数时,如果其他函数在 main 函数的下面,则要在 main 函数上面先声明这个函数。
或者把 main 函数放在最下面,这个不仅限于 main 函数,其他函数的调用都是如此。被调用的函数要在调用的函数之前声明。
自动类型转换
自动转换规则:
1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
4、char型和short型参与运算时,必须先转换成int型。
5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度:
强制类型转换
强制类型转换是通过类型转换运算来实现的。其一般形式为:(类型说明符)(表达式)其功能是把表达式的运算结果强制转换成类型说明符所表示的类型
int a = 1;
double b = 2.1;
cout << "a + b = " << a + (int)b << endl; //输出为a + b = 3
四 变量作用域
1.局部变量和全局变量的名称可以相同,但是在函数内的局部变量与全局变量是两个独立的变量,互不影响。
#include <iostream>
using namespace std;
int main()
{
int b = 2;
{
int b = 1;
cout << "b = " << b << endl;
}
cout << "b = " << b << endl;
}
输出:
b = 1
b = 2
2.当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值
int:0,char:’\0’,float:0,double:0,pointer(指针):NULL
3.全局变量、局部变量、静态全局变量、静态局部变量的区别
从作用域看:
1)全局变量只需在一个源文件中定义,就可以作用于所有的源文件。其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
2)静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
3)局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回
4)静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
从分配内存空间看:
全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间。
1)全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
tips1:静态变量会被放在程序的静态数据存储区(数据段)(全局可见)中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
tips2:变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。
总结:
static 全局变量:改变作用范围,不改变存储位置
static 局部变量:改变存储位置,不改变作用范围
静态函数 :在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
C++ 函数
函数声明
当您在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,您应该在调用函数的文件顶部声明函数。
调用函数
当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
函数参数
则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁
当调用函数时,有三种向函数传递参数的方式:
1)传值调用:该方法把参数的实际值赋值给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
2)指针调用:该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
3)引用调用:该方法把参数的引用赋值给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
C++数字
为了利用这些函数,您需要引用数学头文件 。
double pow(double,double)
double log(double)
double sqrt(double)
int abs(int)
double fabs(double)
double floor(double)
C++随机数
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
int main ()
{
int i,j;
// 设置种子
srand( (unsigned)time( NULL ) );
/* 生成 10 个随机数 */
for( i = 0; i < 10; i++ )
{
// 生成实际的随机数
j= rand();
cout <<"随机数: " << j << endl;
}
return 0;
}
C++数组
静态 int array[100]; 定义了数组 array,并未对数组进行初始化
静态 int array[100] = {1,2}; 定义并初始化了数组 array
动态 int* array = new int[100]; delete []array; 分配了长度为 100 的数组 array
动态 int* array = new int[100]{1,2}; delete []array; 为长度为100的数组array初始化前两个元素
数组在使用时可以是一个含有变量的表达式,但是,在数组声明时必须用常量表达式。例如:
// 合法
const int a=19;
long b[a];
// 合法
const int a=19;
long b[a+5];
// 不合法
int a=19;
long b[a+5];
指向数组的指针
double *p;
double runoobAarray[10];
p = runoobAarray;
一旦您把第一个元素的地址存储在 p 中,您就可以使用 p、(p+1)、*(p+2) 等来访问数组元素
C++中,将 char * 或 char[] 传递给 cout 进行输出,结果会是整个字符串,如果想要获得字符串的地址(第一个字符的内存地址),可使用以下方法:
强制转化为其他指针(非 char*)。可以是 void ,int ,float , double * 等。 使用 &s[0] 不能输出 s[0](首字符)的地址。因为 &s[0] 将返回 char,对于 char(char 指针),cout 会将其作为字符串来处理,向下查找字符并输出直到字符结束 *。
Array 直接初始化 char 数组是特殊的,这种初始化要记得字符是以一个 null 结尾的。‘
char a1[] = {'C', '+', '+'}; // 初始化,没有 null
char a2[] = {'C', '+', '+', '\0'}; // 初始化,明确有 null
char a3[] = "C++"; // null 终止符自动添加
const char a4[6] = "runoob"; // 报错,没有 null 的位置
传递数组给函数
C++ 传数组给一个函数,数组类型自动转换为指针类型,因而传的实际是地址。
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数
1.void myFunction(int *param)
2.void myFunction(int param[10])
3.void myFunction(int param[])
就函数而言,数组的长度是无关紧要的,因为 C++ 不会对形式参数执行边界检查
arr 是一个指针,即为地址,指针占几个字节跟语言无关,而是跟系统的寻址能力有关。
例如:以前是 16 位地址,指针即为 2 个字节,现在一般是 32 位系统,所以是 4 个字节,以后 64 位,则就占 8 个字节。
从函数返回数组
C++中函数是不能直接返回一个数组的,但是数组其实就是指针,所以可以让函数返回指针来实现:
『返回指针的函数』和『指向函数的指针』非常相似,使用时特别注意区分。
返回指针的函数定义:char * upper(char *str)
指向函数的指针:char (*fun)(int int)
C++字符串
C++ 提供了以下两种类型的字符串表示形式:
1.C风格字符串
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char greeting[] = "Hello";
不需要把 null 字符放在字符串常量的末尾。C++ 编译器会在初始化数组时,自动把 ‘\0’ 放在字符串的末尾
操作以‘\0’结尾的字符串函数
1)strcpy(s1,s2):复制s2到s1
2)strcat(s1,s2):连接s2到s1后面
3)strlen(s1)
4)strcmp(s1,s2):s1==s2,返回0;s1<s2,返回值小于0.s1>s2返回值大于0
5)strchr(s1,ch):返回一个指针,指向s1中字符ch第一次出现的位置
6)strstr(s1,s2):返回一个指针,指向字符串s1中s2第一次出现的位置
2.C++中的string类
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "Hello";
string str2 = "World";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
string类提供了一系列针对字符串的操作,比如:
- append() – 在字符串的末尾添加字符
- find() – 在字符串中查找字符串
- insert() – 插入字符
- length() – 返回字符串的长度
- replace() – 替换字符串
- substr() – 返回某个子字符串
C++指针
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同
C++指针数组
数组存储指向 int 或 char 或其他数据类型的指针
int *ptr[MAX];
C++引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++ 引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
1.不存在空引用。引用必须连接到一块合法的内存。
2.一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
3.引用必须在创建时被初始化。指针可以在任何时间被初始化。
int i = 17;
int& r = i;
double& s = d;
引用通常用于函数参数列表和函数返回值.
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用
1)以引用返回函数值,定义函数时需要在函数名前加 &
(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
引用作为返回值,必须遵守以下规则:
(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
关键字集合
关键字:enum
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
enum color { red, green, blue } c;
c = blue;
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
enum color { red, green=5, blue };
在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0
关键字:typedef
typedef type newname; 为已有类型取一个新名字
typedef int feet;
feet distance;
关键字 typedef 在编译阶段有效,由于是在编译阶段,因此 typedef 有类型检查的功能。
typedef unsigned int UINT;
void func()
{
UINT value = "abc"; // error C2440: 'initializing' : cannot convert from 'const char [4]' to 'UINT'
cout << value << endl;
}
typedef 用来定义类型的别名,定义与平台无关的数据类型,与 struct 的结合使用等。
作用域: typedef 有自己的作用域。
关键字:define
宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查
// #define用法例子:
#define f(x) x*x
int main()
{
int a=6, b=2, c;
c=f(a) / f(b);
printf("%d\n", c); //输出36
return 0;
}
功能:#define 不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
作用域:#define 没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。
void func1()
{
#define HW "HelloWorld";
}
void func2()
{
string str = HW;
cout << str << endl;
}
关键字const
定义成 const 后的常量,程序对其中只能读不能修改。
以下程序是错误的,因为开头就已经固定了常量,便不能再对其进行赋值:
#include <iostream>
using namespace std;
int main()
{
const double pi; //圆周率的值用pi表示
pi=3.14159265;
cout<<"圆周率的近似值是"<<pi<<endl;
return 0;
}
类型和安全检查不同:
宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;
const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查
编译器处理不同:
宏定义是一个"编译时"概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;
const常量是一个"运行时"概念,在程序运行使用,类似于一个只读行数据
存储方式不同:
宏定义是直接替换,不会分配内存,存储与程序的代码段中;
const常量需要进行内存分配,存储与程序的数据段中
定义后能否取消:
宏定义可以通过#undef来使之前的宏定义失效
const常量定义后将在定义域内永久有效
关键字volatile
修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。
关键字auto
自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型
关键字static
1.使用 static 修饰局部变量可以在函数调用之间保持局部变量的值
2.当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内
当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
关键字extern
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
main.cpp
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
support.cpp
#include <iostream>
extern int count;
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}