注:原文地址
前言:下文是很基础的C++语法的 notes,只是个人学习 C++时,针对自己不熟悉的知识点做的零散的笔记,方便后期阅读。对于很熟悉 C++的人来说,不具备阅读价值。
学习教程:C++ 教程 | 菜鸟教程
1. 局部变量和全局变量初始化问题
当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值,int - 0, char- ‘\0’,float - 0 等
2. 定义常量方式
C++中有两种方式, C++ 常量 | 菜鸟教程
- 使用宏定义#define预处理器(不需要分号,不需要声明类型)
形式:#define identifier value,如 # define LENGTH 10 - 使用 const 关键字
形式:const type variable = value;
常量名定义和 Java 一样,最好使用大写字母形式,同时注意常量定义必须有值,也不能被重新赋值(不然就不叫常量了)
两种定义方式的区别:
- 类型和安全检查不同
宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;const 常量是常量的声明,有类型区别,需要在编译阶段进行类型检查 - 编译器处理不同
宏定义是一个"编译时"概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;const常量是一个"运行时"概念,在程序运行使用,类似于一个只读行数据 - 存储方式不同
宏定义是直接替换,不会分配内存,存储与程序的代码段中;const常量需要进行内存 - 定义域不同
void f1 ()
{
#define N 12
const int n 12;
}
void f2 ()
{
cout<<N <<endl; //正确,N已经定义过,不受定义域限制
cout<<n <<endl; //错误,n定义域只在f1函数中
}
- 定义后能否取消
宏定义可以通过#undef来使之前的宏定义失效;const 常量定义后将在定义域内永久有效
void f1()
{
#define N 12
const int n = 12;
#undef N //取消宏定义后,即使在f1函数中,N也无效了
#define N 21//取消后可以重新定义
}
- 是否可以做函数参数
宏定义不能作为参数传递给函数
const常量可以在函数的参数列表中出现
另外,还有 typedef 的关键字,用来对一个类型取一个新名字,目的是为了使源代码更易于阅读和理解。如在 JNI 的头部文件会看到这种
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
3. C++修饰符类型
数据类型修饰符:signed, unsigned, long, short
- 修饰整形:signed, unsigned, long, short
- 修饰字符型:signed, unsigned
- 修饰双精度性:long
- long 或 short 的修饰符前缀:signed, unsigned
C++ 速记符号声明无符号短整数或无符号长整数,可以使用 unsigned、short 或 unsigned、long,其中 int 是隐含的
类型限定符
const:对象在程序执行期间不能被修改改变。
volatile:修饰符 volatile 告诉编译器,变量的值可能以程序未明确指定的方式被改变。
restrict:由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。
4. 存储类
1)static 存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
#include <iostream>
// 函数声明
void func(void);
static int count = 4; /* 全局变量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函数定义
void func( void )
{
static int i = 5; // 局部静态变量
i++;
std::cout << "变量 i 为 " << i ;
std::cout << " , 变量 count 为 " << count << std::endl;
}
输出为:
变量 i 为 6 , 变量 count 为 3
变量 i 为 7 , 变量 count 为 2
变量 i 为 8 , 变量 count 为 1
变量 i 为 9 , 变量 count 为 0
2)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;//声明已经在main.cpp 中定义的 count
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
结果为:Count is 5
3)mutable存储类
mutable 说明符仅适用于类的对象。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
4)thread_local 存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
5. 运算符
a. 位运算符
位运算符有点忘记了,做下笔记记录下
1)按位或(OR):p | q
两个数的相应二进制位,只要有1,则为1
2)按位与(AND):p & q
两个数的相应二进制位,只有同为1,才为1
3)按位异或(XOR):p ^ q
两个数的相应二进制位,同则为0,不同为1
4)取反(NOT):~p
二进制位,1变为0 ,0变为1
5)移位
左移:<< ,右移:>>
b. 杂项运算符
1)sizeof——sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。
2)Condition ? X : Y——条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。
3), ** ——逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。
4).(点)和 ->(箭头)—— 成员运算符用于引用类、结构和共用体的成员。
5)Cast** —— 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。
6)& —— 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。
7)* —— *指针运算符 *** 指向一个变量。例如,var; 将指向变量 var。
6. 循环
C++有 goto 语句;
C++ 程序员偏向于使用 for(;;) 结构来表示一个无限循环;
Switch 语句里面的判断类型,必须是一个整型或枚举类型,或者是一个有单一转换函数将其转换为整型或枚举类型的 class 类型;
7. 函数
C++需要在调用函数顶部声明函数,Java 不需要;
函数参数:
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有两种向函数传递参数的方式:
1)传值调用——该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
2)指针调用——该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
3)引用调用——该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
默认情况下,C++ 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的参数。
Lambda函数与表达式
表达式形式:[capture](parameters)->return-type{body}
注意:
宏函数:宏函数用 define 定义,核心是替换,目的为了简化长的函数名字
// 普通函数
void _jni_define_func_read() {
printf("read\n");
}
void _jni_define_func_write() {
printf("write\n");
}
// 定义宏函数
#define jni(NAME) _jni_define_func_##NAME() ;
void main() {
// 宏函数
//jni(read); 可以简化函数名称
jni(write) ;
system("pause");
}
8. 数组
声明方式:
type arrayName [ arraySize ];
如:double balance[10];
初始化:
double balance[3] = {1,4,6}; 或 double balance[] = {1,4,6}
- 多维数组:C++ 支持多维数组。多维数组最简单的形式是二维数组。
- 指向数组的指针:可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。
double *p;
double balance[10];
p = balance;
// balance[4]:
// *(balance + 4) 使用 balance 作为地址的数组值
// *(p + 4) 使用指针的数组值
- 传递数组给函数:您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
C++ 不允许向函数传递一个完整的数组作为参数,但是,您可以通过指定不带索引的数组名来传递一个指向数组的指针。
//形参是一个指针
void myFunction(int *param) {}
//形参是一个已知大小的数组
void myFunction(int param[10]) {}
//形式参数是一个未定义大小的数组
void myFunction(int param[]) {}
- 从函数返回数组:C++ 允许从函数返回数组。
C++ 不允许返回一个完整的数组作为函数的参数。可以通过指定不带索引的数组名来返回一个指向数组的指针。
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int * getRandom( )
{
//C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量
static int r[10];
// 设置种子
srand( (unsigned)time( NULL ) );
for (int i = 0; i < 10; ++i)
{
r[i] = rand();
cout << r[i] << endl;
}
return r;
}
int main ()
{
// 一个指向整数的指针
int *p;
p = getRandom();
for ( int i = 0; i < 10; i++ )
{
cout << "*(p + " << i << ") : ";
cout << *(p + i) << endl;
}
return 0;
}
9. 字符串
C 的字符串:实际上是使用 null 字符 '\0' 终止的一维字符数组,字符串复制、拼接、长度、比较等都需要用函数,strcpy(s1, s2)、strcat(s1, s2)、strlen(s1)、strcmp(s1, s2)……
#include <cstring>
char str1[11] = "Hello";
C++的字符串引入了 string 类型,一般操作和 Java 一样,函数头引入#include <string>
10. 指针
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type var-name;
type 是指针的基类型,它必须是一个有效的 C++ 数据类型
几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值*
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 1. 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 2. 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 3. 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
/*
Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20
*/
1)Null 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
判空:
if(ptr) /* 如果 ptr 非空,则完成 */
if(!ptr) /* 如果 ptr 为空,则完成 */
2)二级指针
就是指针的指针。记住多级指针里面存储的是上级指针的地址即可 。
int i = 10;
// p指针变量存储的是i的内存地址
int* p = &i;
// p1指针变量存储的是p的内存地址
int** p1 = &p;
对应的内存模拟图
3)指针函数
可以将函数定义成为一个函数指针 , 但函数不同于变量 , 变量存储的是固定的值 , 而函数指针存储的是函数的内存地址
int add(int num1, int num2) {
return num1 + num2;
}
int minus(int num1, int num2) {
return num1 - num2;
}
// 将函数指针直接定义到函数形参中 , 类似java中的多态
// 我们可以函数指针作为函数参数传入
void showMsg(int(*c)(int num1, int num2), int a, int b) {
int r = c(a, b);
printf("计算完成=%d\n", r);
}
void main() {
showMsg(add, 10, 10);
showMsg(minus, 30, 2);
}
getchar();
/* 输入:
计算完成=20
计算完成=28
*/
4)动态内存分配
在C语言中 , 我们在堆区开辟一块空间使用的关键字是malloc , malloc函数定义:
void* __cdecl malloc(
_In_ _CRT_GUARDOVERFLOW size_t _Size
);
// 动态内存分配 , 使用malloc函数在对内存中开辟连续的内存空间 , 单位是:字节
// 申请一块40M的堆内存
int* p = (int*)malloc(1024 *1024 * 10 * sizeof(int));
使用malloc申请内存 , 最重要的一个点就是可以动态改变申请的内存大小 , 可以使用realloc函数来重新申请内存大小,realloc函数定义:
void* __cdecl realloc(
_Pre_maybenull_ _Post_invalid_ void* _Block,
_In_ _CRT_GUARDOVERFLOW size_t _Size
);
// 重新申请内存大小 , 传入申请的内存指针 , 申请内存总大小
int* p2 = realloc(p, (len + add) * sizeof(int));
内存分配的几个注意细节:
(1)不能多次释放
(2)释放完之后 , 给指针置NULL,标志释放完成
(3)内存泄漏 (p重新赋值之后 , 再free , 并没有真正释放 , 要在赋值之前释放前一个内存空间)
11. 引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。
引用和指针的区别:
1)不存在空引用。引用必须连接到一块合法的内存。
2)一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
3)引用必须在创建时被初始化。指针可以在任何时间被初始化。
#include <iostream>
using namespace std;
int main ()
{
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i; //r 是一个初始化为 i 的整型引用
double& s = d; //s 是一个初始化为 d 的 double 型引用
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
a. 把引用作为参数,如交换两个数
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
b. 把引用作为返回值
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i )
{
return vals[i]; // 返回第 i 个元素的引用
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
/*
改变前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改变后的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50
*/
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
int& func() {
int q;
//! return q; // 在编译时发生错误
static int x;
return x; // 安全,x 在函数作用域外依然是有效的
}
12. 时间和日期
有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。头文件:#include <ctime>
,C++ 日期 & 时间 | 菜鸟教程
结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:
struct tm {
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
}
13. 输入输出
<iostream> 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
14. 数据结构
struct 语句的格式如下:
struct [structure tag]
{
member definition;
member definition;
...
member definition;
} [one or more structure variables];
访问结构的成员,使用成员访问运算符(.)
1)结构体的两种创建方式,①通过字面量的方式创建 。② 通过定义结构体变量然后给成员赋值 , 类似对象给成员属性赋值。
2)字符数组的赋值,不能直接="xxx",而需要使用strcpy()函数 。
15. 类和对象
没有修饰符,默认为 private
使用初始化列表来初始化字段
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
//....
}
1)类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
2)拷贝构造函数
一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
classname (const classname &obj) {
// 构造函数的主体
}
3)友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
4)内联函数
内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义
5)this 指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
this->method()
6)类的静态成员
声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态数据成员,不能访问其他静态成员函数和类外部的其他函数。
16. 继承
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
不同于 Java,C++支持多重继承。
未完待续……